summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdolfo Fitoria <fitoria@fitoria-laptop.(none)>2010-02-09 14:12:05 -0600
committerAdolfo Fitoria <fitoria@fitoria-laptop.(none)>2010-02-09 14:12:05 -0600
commit8de2b9131ddcef647799cf8e1e79921284523073 (patch)
tree81e17d84530990e35a0accba3a7886266a601482
parent7e95e6481d1e81e43d4b442cbcf3fe37f20d89cc (diff)
parent9d1fb9890b97beb55461ca34f9757bc685461130 (diff)
downloadaskbot-8de2b9131ddcef647799cf8e1e79921284523073.tar.gz
askbot-8de2b9131ddcef647799cf8e1e79921284523073.tar.bz2
askbot-8de2b9131ddcef647799cf8e1e79921284523073.zip
Merge branch 'evgenyfadeev/master'
Conflicts: .gitignore INSTALL TODO cnprog.wsgi django_authopenid/urls.py django_authopenid/views.py drop-all-tables.sh forum/auth.py forum/managers.py forum/models.py forum/templatetags/extra_tags.py forum/views.py locale/es/LC_MESSAGES/django.mo locale/es/LC_MESSAGES/django.po settings.py settings_local.py.dist sql_scripts/update_2009_01_25_001.sql sql_scripts/update_2009_02_26_001.sql sql_scripts/update_2009_04_10_001.sql templates/authopenid/confirm_email.txt templates/authopenid/sendpw_email.txt templates/content/js/compress.bat templates/content/js/flot-build.bat templates/content/style/style.css templates/footer.html templates/question.html templates/user_reputation.html templates/user_stats.html templates/user_votes.html templates/users_questions.html urls.py
-rw-r--r--.gitignore3
-rw-r--r--INSTALL265
-rw-r--r--TODO3
-rw-r--r--cnprog.wsgi8
-rw-r--r--context.py4
-rw-r--r--cron/send_email_alerts4
-rw-r--r--development.log2
-rw-r--r--django_authopenid/external_login.py103
-rw-r--r--django_authopenid/forms.py462
-rw-r--r--django_authopenid/models.py5
-rw-r--r--django_authopenid/urls.py13
-rw-r--r--django_authopenid/util.py7
-rw-r--r--django_authopenid/views.py342
-rw-r--r--drop-all-tables.sh4
-rw-r--r--drop-all.sql39
-rw-r--r--forum/auth.py94
-rw-r--r--forum/const.py7
-rw-r--r--forum/feed.py4
-rw-r--r--forum/forms.py164
-rw-r--r--forum/management/commands/send_email_alerts.py287
-rw-r--r--forum/management/commands/subscribe_everyone.py31
-rw-r--r--forum/managers.py30
-rw-r--r--forum/models.py207
-rw-r--r--forum/sitemap.py11
-rw-r--r--forum/templatetags/extra_filters.py20
-rw-r--r--forum/templatetags/extra_tags.py127
-rw-r--r--forum/templatetags/smart_if.py401
-rw-r--r--forum/urls.py91
-rw-r--r--forum/user.py12
-rw-r--r--forum/views.py1204
-rw-r--r--junk.py3
-rw-r--r--locale/en/LC_MESSAGES/django.mobin15146 -> 25611 bytes
-rw-r--r--locale/en/LC_MESSAGES/django.po2443
-rw-r--r--locale/es/LC_MESSAGES/django.mobin49749 -> 367 bytes
-rw-r--r--locale/es/LC_MESSAGES/django.po3201
-rw-r--r--middleware/__init__.py~HEAD (renamed from log/cnprog.log)0
-rw-r--r--middleware/anon_user.py34
-rw-r--r--middleware/cancel.py15
-rw-r--r--middleware/pagesize.py6
-rw-r--r--migration7
-rw-r--r--session_messages/.svn/all-wcprops23
-rw-r--r--session_messages/.svn/dir-prop-base6
-rw-r--r--session_messages/.svn/entries64
-rw-r--r--session_messages/.svn/format1
-rw-r--r--session_messages/.svn/text-base/__init__.py.svn-base36
-rw-r--r--session_messages/.svn/text-base/context_processors.py.svn-base48
-rw-r--r--session_messages/.svn/text-base/models.py.svn-base3
-rw-r--r--session_messages/__init__.py37
-rw-r--r--session_messages/context_processors.py48
-rw-r--r--session_messages/models.py3
-rw-r--r--settings.py31
-rw-r--r--settings_local.py.dist79
-rw-r--r--sphinx/sphinx.conf127
-rw-r--r--sql_scripts/091111_upgrade_evgeny.sql1
-rw-r--r--sql_scripts/091208_upgrade_evgeny.sql1
-rw-r--r--sql_scripts/091208_upgrade_evgeny_1.sql1
-rw-r--r--sql_scripts/update_2009_01_25_001.sql2
-rw-r--r--sql_scripts/update_2009_02_26_001.sql2
-rw-r--r--sql_scripts/update_2009_04_10_001.sql2
-rw-r--r--tables.sql440
-rw-r--r--templates/404.html2
-rw-r--r--templates/about.html28
-rw-r--r--templates/answer_edit.html13
-rw-r--r--templates/answer_edit_tips.html8
-rw-r--r--templates/ask.html19
-rw-r--r--templates/authopenid/changeemail.html45
-rw-r--r--templates/authopenid/changeopenid.html2
-rw-r--r--templates/authopenid/changepw.html33
-rw-r--r--templates/authopenid/complete.html86
-rw-r--r--templates/authopenid/confirm_email.txt4
-rw-r--r--templates/authopenid/delete.html3
-rw-r--r--templates/authopenid/external_legacy_login_info.html21
-rw-r--r--templates/authopenid/sendpw.html30
-rw-r--r--templates/authopenid/sendpw_email.txt14
-rw-r--r--templates/authopenid/settings.html2
-rw-r--r--templates/authopenid/signin.html70
-rw-r--r--templates/authopenid/signup.html59
-rw-r--r--templates/badges.html6
-rw-r--r--templates/base.html23
-rw-r--r--templates/base_content.html28
-rw-r--r--templates/book.html6
-rw-r--r--templates/content/images/blue-up-arrow-h18px.pngbin0 -> 593 bytes
-rw-r--r--templates/content/images/close-small-dark.pngbin0 -> 226 bytes
-rw-r--r--templates/content/images/gray-up-arrow-h18px.pngbin0 -> 383 bytes
-rw-r--r--templates/content/images/logo.gif (renamed from templates/content/images/cnprog_logo_200_56.gif)bin2114 -> 2114 bytes
-rw-r--r--templates/content/images/logo.pngbin3631 -> 1902 bytes
-rw-r--r--templates/content/jquery-openid/jquery.openid.js8
-rw-r--r--templates/content/jquery-openid/openid.css36
-rw-r--r--templates/content/js/com.cnprog.admin.js17
-rw-r--r--templates/content/js/com.cnprog.i18n.js34
-rw-r--r--templates/content/js/com.cnprog.post.js390
-rw-r--r--templates/content/js/com.cnprog.tag_selector.js168
-rw-r--r--templates/content/js/com.cnprog.utils.js15
-rw-r--r--templates/content/js/compress.bat3
-rw-r--r--templates/content/js/flot-build.bat2
-rw-r--r--templates/content/js/jquery.form.js654
-rw-r--r--templates/content/js/wmd/wmd.js6
-rw-r--r--templates/content/style/default.css27
-rw-r--r--templates/content/style/jquery.autocomplete.css2
-rw-r--r--templates/content/style/style.css675
-rw-r--r--templates/edit_user_email_feeds_form.html4
-rw-r--r--templates/faq.html6
-rw-r--r--templates/feedback.html55
-rw-r--r--templates/feedback_email.txt19
-rw-r--r--templates/footer.html52
-rw-r--r--templates/header.html28
-rw-r--r--templates/index.html78
-rw-r--r--templates/logout.html1
-rw-r--r--templates/post_contributor_info.html55
-rw-r--r--templates/question.html579
-rw-r--r--templates/question_edit.html17
-rw-r--r--templates/question_edit_tips.html9
-rw-r--r--templates/question_retag.html13
-rw-r--r--templates/question_summary_list_roll.html55
-rw-r--r--templates/questions.html176
-rw-r--r--templates/revisions_answer.html41
-rw-r--r--templates/revisions_question.html41
-rw-r--r--templates/tag_selector.html42
-rw-r--r--templates/tags.html2
-rw-r--r--templates/unanswered.html66
-rwxr-xr-xtemplates/upfiles/1245715031297631.pngbin3863 -> 0 bytes
-rwxr-xr-xtemplates/upfiles/12457157052552259.pngbin3863 -> 0 bytes
-rw-r--r--templates/user.html30
-rw-r--r--templates/user_edit.html8
-rw-r--r--templates/user_email_subscriptions.html29
-rw-r--r--templates/user_favorites.html1
-rw-r--r--templates/user_info.html42
-rw-r--r--templates/user_preferences.html24
-rw-r--r--templates/user_recent.html1
-rw-r--r--templates/user_reputation.html7
-rw-r--r--templates/user_stats.html79
-rw-r--r--templates/user_tabs.html6
-rw-r--r--templates/user_votes.html9
-rw-r--r--templates/users.html8
-rw-r--r--templates/users_questions.html34
-rw-r--r--urls.py67
-rw-r--r--user_messages/__init__.py36
-rw-r--r--user_messages/context_processors.py52
-rw-r--r--user_messages/models.py3
-rw-r--r--utils/decorators.py25
-rw-r--r--utils/odict.py1399
141 files changed, 11948 insertions, 4408 deletions
diff --git a/.gitignore b/.gitignore
index 759a0451..69133c46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,5 @@
*.pyc
*.swp
*.log
-settings_local.py
nbproject
settings_local.py
-log
-*.log
diff --git a/INSTALL b/INSTALL
index 0c5e76dc..7de10871 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,10 +1,27 @@
-PRE-REQUIREMENTS:
+CONTENTS
+------------------
+A. PREREQUISITES
+B. INSTALLATION
+ 1. Settings file
+ 2. Database
+ 3. Running CNPROG in the development server
+ 4. Installation under Apache/WSGI
+ 5. Full text search
+ 6. Email subscriptions
+ 7. Sitemap
+ 8. Miscellaneous
+C. CONFIGURATION PARAMETERS (settings_local.py)
+D. CUSTOMIZATION
+
+
+A. PREREQUISITES
-----------------------------------------------
0. We recommend you to use python-setuptools to install pre-requirement libraries.
If you haven't installed it, please try to install it first.
e.g, sudo apt-get install python-setuptools
1. Python2.5/2.6, MySQL, Django v1.0/1.1
+Note: email subscription sender job requires Django 1.1, everything else works with 1.0
Make sure mysql for python provider has been installed.
sudo easy_install mysql-python
@@ -12,9 +29,6 @@ sudo easy_install mysql-python
http://openidenabled.com/python-openid/
sudo easy_install python-openid
-3. django-authopenid(Included in project already)
-http://code.google.com/p/django-authopenid/
-
4. html5lib
http://code.google.com/p/html5lib/
Used for HTML sanitizer
@@ -27,24 +41,257 @@ sudo easy_install markdown2
6. Django Debug Toolbar
http://github.com/robhudson/django-debug-toolbar/tree/master
-INSTALL STEPS:
+7. djangosphinx (optional - for full text questions+answer+tag)
+http://github.com/dcramer/django-sphinx/tree/master/djangosphinx
+
+8. sphinx search engine (optional, works together with djangosphinx)
+http://sphinxsearch.com/downloads.html
+
+NOTES: django_authopenid is included into CNPROG code
+and is significantly modified. http://code.google.com/p/django-authopenid/
+no need to install this library
+
+B. INSTALLATION
-----------------------------------------------
0. Make sure you have all above python libraries installed.
-1. Copy settings_local.py.dist to settings_local.py and
+ make cnprog installation server-readable on Linux command might be:
+ chown -R yourlogin:apache /path/to/CNPROG
+
+ directories templates/upfiles and log must be server writable
+
+ on Linux type chmod
+ chmod -R g+w /path/to/CNPROG/upfiles
+ chmod -R g+w /path/to/log
+
+ above it is assumed that webserver runs under group named "apache"
+
+1. Settings file
+
+Copy settings_local.py.dist to settings_local.py and
update all your settings. Check settings.py and update
it as well if necessory.
+Section C explains configuration paramaters.
-2. Prepare your database by using the same database/account
+2. Database
+
+Prepare your database by using the same database/account
configuration from above.
e.g,
create database cnprog DEFAULT CHARACTER SET UTF8 COLLATE utf8_general_ci;
grant all on cnprog.* to 'cnprog'@'localhost';
And then run "python manage.py syncdb" to synchronize your database.
-3. Run "python manager.py runserver" to startup django
+3. Running CNPROG on the development server
+
+Run "python manage.py runserver" to startup django
development environment.
+(Under Linux you can use command "python manage.py runserver `hostname -i`:8000",
+where you can use any other available number for the port)
+
+you might want to have DEBUG=True in the beginning of settings.py
+when using the test server
+
+4. Installation under Apache/WSGI
+
+4.1 Prepare wsgi script
+
+Make a file readable by your webserver with the following content:
+
+---------
+import os
+import sys
+
+sys.path.insert(0,'/one/level/above') #insert to make sure that forum will be found
+sys.path.append('/one/level/above/CNPROG') #maybe this is not necessary
+os.environ['DJANGO_SETTINGS_MODULE'] = 'CNPROG.settings'
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
+-----------
+
+insert method is used for path because if the forum directory name
+is by accident the same as some other python module
+you wull see strange errors - forum won't be found even though
+it's in the python path. for example using name "test" is
+not a good idea - as there is a module with such name
+
+
+4.2 Configure webserver
+Settings below are not perfect but may be a good starting point
+
+---------
+WSGISocketPrefix /path/to/socket/sock #must be readable and writable by apache
+WSGIPythonHome /usr/local #must be readable by apache
+WSGIPythonEggs /var/python/eggs #must be readable and writable by apache
+
+#NOTE: all urs below will need to be adjusted if
+#settings.FORUM_SCRIPT_ALIAS !='' (e.g. = 'forum/')
+#this allows "rooting" forum at http://example.com/forum, if you like
+<VirtualHost ...your ip...:80>
+ ServerAdmin forum@example.com
+ DocumentRoot /path/to/cnprog
+ ServerName example.com
+
+ #run mod_wsgi process for django in daemon mode
+ #this allows avoiding confused timezone settings when
+ #another application runs in the same virtual host
+ WSGIDaemonProcess CNPROG
+ WSGIProcessGroup CNPROG
+
+ #force all content to be served as static files
+ #otherwise django will be crunching images through itself wasting time
+ Alias /content/ /path/to/cnprog/templates/content/
+ AliasMatch /([^/]*\.css) /path/to/cnprog/templates/content/style/$1
+ <Directory /path/to/cnprog/templates/content>
+ Order deny,allow
+ Allow from all
+ </Directory>
+
+ #this is your wsgi script described in the prev section
+ WSGIScriptAlias / /path/to/cnprog/cnprog.wsgi
-4. There are some demo scripts under sql_scripts folder,
+ #this will force admin interface to work only
+ #through https (optional)
+ #"nimda" is the secret spelling of "admin" ;)
+ <Location "/nimda">
+ RewriteEngine on
+ RewriteRule /nimda(.*)$ https://example.com/nimda$1 [L,R=301]
+ </Location>
+ CustomLog /var/log/httpd/CNPROG/access_log common
+ ErrorLog /var/log/httpd/CNPROG/error_log
+</VirtualHost>
+#(optional) run admin interface under https
+<VirtualHost ..your ip..:443>
+ ServerAdmin forum@example.com
+ DocumentRoot /path/to/cnrpog
+ ServerName example.com
+ SSLEngine on
+ SSLCertificateFile /path/to/ssl-certificate/server.crt
+ SSLCertificateKeyFile /path/to/ssl-certificate/server.key
+ WSGIScriptAlias / /path/to/cnprogcnprog.wsgi
+ CustomLog /var/log/httpd/CNPROG/access_log common
+ ErrorLog /var/log/httpd/CNPROG/error_log
+ DirectoryIndex index.html
+</VirtualHost>
+-------------
+
+5. Full text search (using sphinx search)
+ Currently full text search works only with sphinx search engine
+ Sphinx at this time supports only MySQL and PostgreSQL databases
+ to enable this, install sphinx search engine and djangosphinx
+
+ configure sphinx, sample configuration can be found in
+ sphinx/sphinx.conf file usually goes somewhere in /etc tree
+
+ build cnprog index first time manually
+
+ % indexer --config /path/to/sphinx.conf --index cnprog
+
+ setup cron job to rebuild index periodically with command
+ your crontab entry may be something like
+
+ 0 9,15,21 * * * /usr/local/bin/indexer --config /etc/sphinx/sphinx.conf --all --rotate >/dev/null 2>&1
+ adjust it as necessary this one will reindex three times a day at 9am 3pm and 9pm
+
+ if your forum grows very big ( good luck with that :) you'll
+ need to two search indices one diff index and one main
+ please refer to online sphinx search documentation for the information
+ on the subject http://sphinxsearch.com/docs/
+
+ in settings_local.py set
+ USE_SPHINX_SEARCH=True
+ adjust other settings that have SPHINX_* prefix accordingly
+ remember that there must be trailing comma in parentheses for
+ SHPINX_SEARCH_INDICES tuple - particlarly with just one item!
+
+6. Email subscriptions
+
+ This function at the moment requires Django 1.1
+
+ edit paths in the file cron/send_email_alerts
+ set up a cron job to call cron/send_email_alerts once or twice a day
+ subscription sender may be tested manually in shell
+ by calling cron/send_email_alerts
+
+7. Sitemap
+Sitemap will be available at /<settings_local.FORUM_SCRIPT_ALIAS>sitemap.xml
+e.g yoursite.com/forum/sitemap.xml
+
+google will be pinged each time question, answer or
+comment is saved or a question deleted
+
+for this to be useful - do register you sitemap with Google at
+https://www.google.com/webmasters/tools/
+
+8. Miscellaneous
+
+There are some demo scripts under sql_scripts folder,
including badges and test accounts for CNProg.com. You
don't need them to run your sample.
+
+C. CONFIGURATION PARAMETERS
+
+#the only parameter that needs to be touched in settings.py is
+DEBUG=False #set to True to enable debug mode
+
+#all forum parameters are set in file settings_local.py
+
+LOG_FILENAME = 'cnprog.log' #where logging messages should go
+DATABASE_NAME = 'cnprog' # Or path to database file if using sqlite3.
+DATABASE_USER = '' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_ENGINE = 'mysql' #mysql, etc
+SERVER_EMAIL = ''
+DEFAULT_FROM_EMAIL = ''
+EMAIL_HOST_USER = ''
+EMAIL_HOST_PASSWORD = '' #not necessary if mailserver is run on local machine
+EMAIL_SUBJECT_PREFIX = '[CNPROG] '
+EMAIL_HOST='cnprog.com'
+EMAIL_PORT='25'
+EMAIL_USE_TLS=False
+TIME_ZONE = 'America/Tijuana'
+APP_TITLE = u'CNPROG Q&A Forum' #title of your forum
+APP_KEYWORDS = u'CNPROG,forum,community' #keywords for search engines
+APP_DESCRIPTION = u'Ask and answer questions.' #site description for searche engines
+APP_INTRO = u'<p>Ask and answer questions, make the world better!</p>' #slogan that goes to front page in logged out mode
+APP_COPYRIGHT = '' #copyright message
+
+#if you set FORUM_SCRIPT_ALIAS= 'forum/'
+#then CNPROG will run at url http://example.com/forum
+#FORUM_SCRIPT_ALIAS cannot have leading slash, otherwise it can be set to anything
+FORUM_SCRIPT_ALIAS = '' #no leading slash, default = '' empty string
+
+LANGUAGE_CODE = 'en' #forum language (see language instructions on the wiki)
+EMAIL_VALIDATION = 'off' #string - on|off
+MIN_USERNAME_LENGTH = 1
+EMAIL_UNIQUE = False #if True, email addresses must be unique in all accounts
+APP_URL = 'http://cnprog.com' #used by email notif system and RSS
+GOOGLE_SITEMAP_CODE = '' #code for google site crawler (look up google webmaster tools)
+GOOGLE_ANALYTICS_KEY = '' #key to enable google analytics on this site
+BOOKS_ON = False #if True - books tab will be on
+WIKI_ON = True #if False - community wiki feature is disabled
+
+#experimental - allow password login through external site
+#must implement django_authopenid/external_login.py
+#included prototype external_login works with Mediawiki
+USE_EXTERNAL_LEGACY_LOGIN = True #if false CNPROG uses it's own login/password
+EXTERNAL_LEGACY_LOGIN_HOST = 'login.cnprog.com'
+EXTERNAL_LEGACY_LOGIN_PORT = 80
+EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = '<span class="orange">CNPROG</span>'
+
+FEEDBACK_SITE_URL = None #None or url
+LOGIN_URL = '/%s%s%s' % (FORUM_SCRIPT_ALIAS,'account/','signin/')
+
+DJANGO_VERSION = 1.1 #must be either 1.0 or 1.1
+RESOURCE_REVISION=4 #increment when you update media files - clients will be forced to load new version
+
+D. Customization
+
+Other than settings_local.py the following will most likely need customization:
+* locale/*/django.po - language files that may also contain your site-specific messages
+ if you want to start with english messages file - look for words like "forum" and
+ "CNPROG" in the msgstr lines
+* templates/header.html and templates/footer.html may contain extra links
+* templates/about.html - a place to explain for is your forum for
+* templates/faq.html - put answers to users frequent questions
+* templates/content/style/style.css - modify style sheet to add disctinctive look to your forum
diff --git a/TODO b/TODO
new file mode 100644
index 00000000..372e714f
--- /dev/null
+++ b/TODO
@@ -0,0 +1,3 @@
+* per-tag email subscriptions
+* make sorting tabs work in question search
+* allow multiple logins to the same account
diff --git a/cnprog.wsgi b/cnprog.wsgi
new file mode 100644
index 00000000..bd3745ee
--- /dev/null
+++ b/cnprog.wsgi
@@ -0,0 +1,8 @@
+#example wsgi setup script
+import os
+import sys
+sys.path.append('/path/above_forum')
+sys.path.append('/path/above_forum/forum_dir')
+os.environ['DJANGO_SETTINGS_MODULE'] = 'forum_dir.settings'
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/context.py b/context.py
index 3da69f7b..26d326a7 100644
--- a/context.py
+++ b/context.py
@@ -11,6 +11,10 @@ def application_settings(context):
'GOOGLE_SITEMAP_CODE':settings.GOOGLE_SITEMAP_CODE,
'GOOGLE_ANALYTICS_KEY':settings.GOOGLE_ANALYTICS_KEY,
'BOOKS_ON':settings.BOOKS_ON,
+ 'WIKI_ON':settings.WIKI_ON,
+ 'USE_EXTERNAL_LEGACY_LOGIN':settings.USE_EXTERNAL_LEGACY_LOGIN,
+ 'RESOURCE_REVISION':settings.RESOURCE_REVISION,
+ 'USE_SPHINX_SEARCH':settings.USE_SPHINX_SEARCH,
}
return {'settings':my_settings}
diff --git a/cron/send_email_alerts b/cron/send_email_alerts
new file mode 100644
index 00000000..e9e433be
--- /dev/null
+++ b/cron/send_email_alerts
@@ -0,0 +1,4 @@
+PYTHONPATH=/dir/just/above_forum
+export PYTHONPATH
+APP_ROOT=$PYTHONPATH/CNPROG
+/usr/local/bin/python $APP_ROOT/manage.py send_email_alerts >> $APP_ROOT/log/django.lanai.log
diff --git a/development.log b/development.log
index 5310f70b..abe1aac0 100644
--- a/development.log
+++ b/development.log
@@ -1,5 +1,5 @@
==Aug 5, 2009 Evgeny==
-===Interface changes===
+====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
diff --git a/django_authopenid/external_login.py b/django_authopenid/external_login.py
new file mode 100644
index 00000000..bd49c009
--- /dev/null
+++ b/django_authopenid/external_login.py
@@ -0,0 +1,103 @@
+#this file contains stub functions that can be extended to support
+#connect legacy login with external site
+import settings
+from django_authopenid.models import ExternalLoginData
+import httplib
+import urllib
+import Cookie
+import cookielib
+from django import forms
+import xml.dom.minidom as xml
+import logging
+
+def login(request,user):
+ """performs the additional external login operation
+ """
+ pass
+
+def set_login_cookies(response,user):
+ #should be unique value by design
+ try:
+ eld = ExternalLoginData.objects.get(user=user)
+
+ data = eld.external_session_data
+ dom = xml.parseString(data)
+ login_response = dom.getElementsByTagName('login')[0]
+ userid = login_response.getAttribute('lguserid')
+ username = login_response.getAttribute('lgusername')
+ token = login_response.getAttribute('lgtoken')
+ prefix = login_response.getAttribute('cookieprefix').decode('utf-8')
+ sessionid = login_response.getAttribute('sessionid')
+
+ c = {}
+ c[prefix + 'UserName'] = username
+ c[prefix + 'UserID'] = userid
+ c[prefix + 'Token'] = token
+ c[prefix + '_session'] = sessionid
+
+ #custom code that copies cookies from external site
+ #not sure how to set paths and domain of cookies here
+ for key in c:
+ if c[key]:
+ response.set_cookie(str(key),value=str(c[key]))
+ except ExternalLoginData.DoesNotExist:
+ #this must be an OpenID login
+ pass
+
+#function to perform external logout, if needed
+def logout(request):
+ pass
+
+#should raise User.DoesNotExist or pass
+def clean_username(username):
+ return username
+
+def check_password(username,password):
+ """connects to external site and submits username/password pair
+ return True or False depending on correctness of login
+ saves remote unique id and remote session data in table ExternalLoginData
+ may raise forms.ValidationError
+ """
+ host = settings.EXTERNAL_LEGACY_LOGIN_HOST
+ port = settings.EXTERNAL_LEGACY_LOGIN_PORT
+ ext_site = httplib.HTTPConnection(host,port)
+
+ #custom code. this one does authentication through
+ #MediaWiki API
+ params = urllib.urlencode({'action':'login','format':'xml',
+ 'lgname':username,'lgpassword':password})
+ headers = {"Content-type": "application/x-www-form-urlencoded",
+ 'User-Agent':"User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7",
+ "Accept": "text/xml"}
+ ext_site.request("POST","/wiki/api.php",params,headers)
+ response = ext_site.getresponse()
+ if response.status != 200:
+ raise forms.ValidationError('error ' + response.status + ' ' + response.reason)
+ data = response.read().strip()
+ ext_site.close()
+
+ dom = xml.parseString(data)
+ login = dom.getElementsByTagName('login')[0]
+ result = login.getAttribute('result')
+
+ if result == 'Success':
+ username = login.getAttribute('lgusername')
+ try:
+ eld = ExternalLoginData.objects.get(external_username=username)
+ except ExternalLoginData.DoesNotExist:
+ eld = ExternalLoginData()
+ eld.external_username = username
+ eld.external_session_data = data
+ eld.save()
+ return True
+ else:
+ error = login.getAttribute('details')
+ raise forms.ValidationError(error)
+ return False
+
+def createuser(username,email,password):
+ pass
+
+#retrieve email address
+def get_email(username,password):
+ return ''
diff --git a/django_authopenid/forms.py b/django_authopenid/forms.py
index 690a781f..d4482751 100644
--- a/django_authopenid/forms.py
+++ b/django_authopenid/forms.py
@@ -35,8 +35,10 @@ from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from django.utils.translation import ugettext as _
from django.conf import settings
-
+import external_login
+import types
import re
+from django.utils.safestring import mark_safe
# needed for some linux distributions like debian
@@ -46,16 +48,110 @@ except ImportError:
from yadis import xri
from django_authopenid.util import clean_next
+from django_authopenid.models import ExternalLoginData
+
+__all__ = ['OpenidSigninForm', 'ClassicLoginForm', 'OpenidVerifyForm',
+ 'OpenidRegisterForm', 'ClassicRegisterForm', 'ChangePasswordForm',
+ 'ChangeEmailForm', 'EmailPasswordForm', 'DeleteForm',
+ 'ChangeOpenidForm']
-__all__ = ['OpenidSigninForm', 'OpenidAuthForm', 'OpenidVerifyForm',
- 'OpenidRegisterForm', 'RegistrationForm', 'ChangepwForm',
- 'ChangeemailForm', 'EmailPasswordForm', 'DeleteForm',
- 'ChangeOpenidForm', 'ChangeEmailForm', 'ChangepwForm']
+class NextUrlField(forms.CharField):
+ def __init__(self):
+ super(NextUrlField,self).__init__(max_length = 255,widget = forms.HiddenInput(),required = False)
+ def clean(self,value):
+ return clean_next(value)
+
+attrs_dict = { 'class': 'required login' }
+
+class UserNameField(forms.CharField):
+ username_re = re.compile(r'^[\w ]+$')
+ RESERVED_NAMES = (u'fuck', u'shit', u'ass', u'sex', u'add',
+ u'edit', u'save', u'delete', u'manage', u'update', 'remove', 'new')
+ def __init__(self,must_exist=False,skip_clean=False,label=_('choose a username'),**kw):
+ self.must_exist = must_exist
+ self.skip_clean = skip_clean
+ super(UserNameField,self).__init__(max_length=30,
+ widget=forms.TextInput(attrs=attrs_dict),
+ label=label,
+ error_messages={'required':_('user name is required'),
+ 'taken':_('sorry, this name is taken, please choose another'),
+ 'forbidden':_('sorry, this name is not allowed, please choose another'),
+ 'missing':_('sorry, there is no user with this name'),
+ 'multiple-taken':_('sorry, we have a serious error - user name is taken by several users'),
+ 'invalid':_('user name can only consist of letters, empty space and underscore'),
+ },
+ **kw
+ )
+
+ def clean(self,username):
+ """ validate username """
+ username = super(UserNameField,self).clean(username.strip())
+ if self.skip_clean == True:
+ return username
+ if not username_re.search(username):
+ raise forms.ValidationError(self.error_messages['invalid'])
+ if username in self.RESERVED_NAMES:
+ raise forms.ValidationError(self.error_messages['forbidden'])
+ try:
+ user = User.objects.get(
+ username__exact = username
+ )
+ if user:
+ if self.must_exist:
+ return username
+ else:
+ raise forms.ValidationError(self.error_messages['taken'])
+ except User.DoesNotExist:
+ if self.must_exist:
+ raise forms.ValidationError(self.error_messages['missing'])
+ else:
+ return username
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(self.error_messages['multiple-taken'])
+
+class UserEmailField(forms.EmailField):
+ def __init__(self,skip_clean=False,**kw):
+ self.skip_clean = skip_clean
+ super(UserEmailField,self).__init__(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=200)), label=mark_safe(_('your email address')),
+ error_messages={'required':_('email address is required'),
+ 'invalid':_('please enter a valid email address'),
+ 'taken':_('this email is already used by someone else, please choose another'),
+ },
+ **kw
+ )
+
+ def clean(self,email):
+ """ validate if email exist in database
+ from legacy register
+ return: raise error if it exist """
+ email = super(UserEmailField,self).clean(email.strip())
+ if self.skip_clean:
+ return email
+ if settings.EMAIL_UNIQUE == True:
+ try:
+ user = User.objects.get(email = email)
+ raise forms.ValidationError(self.error_messsages['taken'])
+ except User.DoesNotExist:
+ return email
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(self.error_messages['taken'])
+ else:
+ return email
+
+def clean_nonempty_field_method(self,field):
+ value = None
+ if field in self.cleaned_data:
+ value = str(self.cleaned_data[field]).strip()
+ if value == '':
+ value = None
+ self.cleaned_data[field] = value
+ return value
class OpenidSigninForm(forms.Form):
""" signin form """
openid_url = forms.CharField(max_length=255, widget=forms.widgets.TextInput(attrs={'class': 'openid-login-input', 'size':80}))
- next = forms.CharField(max_length=255, widget=forms.HiddenInput(), required=False)
+ next = NextUrlField()
def clean_openid_url(self):
""" test if openid is accepted """
@@ -67,76 +163,98 @@ class OpenidSigninForm(forms.Form):
raise forms.ValidationError(_('i-names are not supported'))
return self.cleaned_data['openid_url']
- def clean_next(self):
- """ validate next """
- if 'next' in self.cleaned_data and self.cleaned_data['next'] != "":
- self.cleaned_data['next'] = clean_next(self.cleaned_data['next'])
- return self.cleaned_data['next']
-
-
-attrs_dict = { 'class': 'required login' }
-username_re = re.compile(r'^\w+$')
-RESERVED_NAMES = (u'fuck', u'shit', u'ass', u'sex', u'add',
- u'edit', u'save', u'delete', u'manage', u'update', 'remove', 'new')
-
-class OpenidAuthForm(forms.Form):
+class ClassicLoginForm(forms.Form):
""" legacy account signin form """
- next = forms.CharField(max_length=255, widget=forms.HiddenInput(),
- required=False)
- username = forms.CharField(max_length=30,
- widget=forms.widgets.TextInput(attrs=attrs_dict))
+ next = NextUrlField()
+ username = UserNameField(required=False,skip_clean=True)
password = forms.CharField(max_length=128,
- widget=forms.widgets.PasswordInput(attrs=attrs_dict))
-
+ widget=forms.widgets.PasswordInput(attrs=attrs_dict), required=False)
+
def __init__(self, data=None, files=None, auto_id='id_%s',
prefix=None, initial=None):
- super(OpenidAuthForm, self).__init__(data, files, auto_id,
+ super(ClassicLoginForm, self).__init__(data, files, auto_id,
prefix, initial)
self.user_cache = None
-
+
+ clean_nonempty_field = clean_nonempty_field_method
+
def clean_username(self):
- """ validate username and test if it exists."""
- if 'username' in self.cleaned_data and \
- 'openid_url' not in self.cleaned_data:
- if not username_re.search(self.cleaned_data['username']):
- raise forms.ValidationError(_("Usernames can only contain \
- letters, numbers and underscores"))
- try:
- user = User.objects.get(
- username__exact = self.cleaned_data['username']
- )
- except User.DoesNotExist:
- raise forms.ValidationError(_("This username does not exist \
- in our database. Please choose another."))
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(u'There is already more than one \
- account registered with that username. Please try \
- another.')
- return self.cleaned_data['username']
+ return self.clean_nonempty_field('username')
def clean_password(self):
- """" test if password is valid for this username """
- if 'username' in self.cleaned_data and \
- 'password' in self.cleaned_data:
- self.user_cache = authenticate(
- username=self.cleaned_data['username'],
- password=self.cleaned_data['password']
- )
+ return self.clean_nonempty_field('password')
+
+ def clean(self):
+ """
+ this clean function actuall cleans username and password
+
+ test if password is valid for this username
+ this is really the "authenticate" function
+ also openid_auth is not an authentication backend
+ since it's written in a way that does not comply with
+ the Django convention
+ """
+
+ error_list = []
+ username = self.cleaned_data['username']
+ password = self.cleaned_data['password']
+
+ self.user_cache = None
+ if username and password:
+
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ pw_ok = False
+ try:
+ pw_ok = external_login.check_password(username,password)
+ except forms.ValidationError, e:
+ error_list.extend(e.messages)
+ if pw_ok:
+ external_user = ExternalLoginData.objects.get(external_username=username)
+ if external_user.user == None:
+ return self.cleaned_data
+ user = external_user.user
+ openid_logins = user.userassociation_set.all()
+
+ if len(openid_logins) > 0:
+ msg1 = _('Account with this name already exists on the forum')
+ msg2 = _('can\'t have two logins to the same account yet, sorry.')
+ error_list.append(msg1)
+ error_list.append(msg2)
+ self._errors['__all__'] = forms.util.ErrorList(error_list)
+ return self.cleaned_data
+ else:
+ #synchronize password with external login
+ user.set_password(password)
+ user.save()
+ #this auth will always succeed
+ self.user_cache = authenticate(username=user.username,\
+ password=password)
+ else:
+ #keep self.user_cache == None
+ #nothing to do, error message will be set below
+ pass
+ else:
+ self.user_cache = authenticate(username=username, password=password)
+
if self.user_cache is None:
- raise forms.ValidationError(_("Please enter a valid \
- username and password. Note that both fields are \
- case-sensitive."))
+ del self.cleaned_data['username']
+ del self.cleaned_data['password']
+ error_list.insert(0,(_("Please enter valid username and password "
+ "(both are case-sensitive).")))
elif self.user_cache.is_active == False:
- raise forms.ValidationError(_("This account is inactive."))
- return self.cleaned_data['password']
+ error_list.append(_("This account is inactive."))
+ if len(error_list) > 0:
+ error_list.insert(0,_('Login failed.'))
+ elif password == None and username == None:
+ error_list.append(_('Please enter username and password'))
+ elif password == None:
+ error_list.append(_('Please enter your password'))
+ elif username == None:
+ error_list.append(_('Please enter user name'))
+ if len(error_list) > 0:
+ self._errors['__all__'] = forms.util.ErrorList(error_list)
+ return self.cleaned_data
- def clean_next(self):
- """ validate next url """
- if 'next' in self.cleaned_data and \
- self.cleaned_data['next'] != "":
- self.cleaned_data['next'] = clean_next(self.cleaned_data['next'])
- return self.cleaned_data['next']
-
def get_user(self):
""" get authenticated user """
return self.user_cache
@@ -144,56 +262,14 @@ class OpenidAuthForm(forms.Form):
class OpenidRegisterForm(forms.Form):
""" openid signin form """
- next = forms.CharField(max_length=255, widget=forms.HiddenInput(),
- required=False)
- username = forms.CharField(max_length=30,
- widget=forms.widgets.TextInput(attrs=attrs_dict))
- email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
- maxlength=200)), label=u'Email address')
-
- def clean_username(self):
- """ test if username is valid and exist in database """
- if 'username' in self.cleaned_data:
- if not username_re.search(self.cleaned_data['username']):
- 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']) < settings.MIN_USERNAME_LENGTH:
- raise forms.ValidationError(_('username too short'))
- try:
- user = User.objects.get(
- username__exact = self.cleaned_data['username']
- )
- except User.DoesNotExist:
- return self.cleaned_data['username']
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(_('this name is already in use - please try anoter'))
- raise forms.ValidationError(_('this name is already in use - please try anoter'))
-
- def clean_email(self):
- """Optionally, for security reason one unique email in database"""
- if 'email' in self.cleaned_data:
- 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']
- #what if not???
-
+ next = NextUrlField()
+ username = UserNameField()
+ email = UserEmailField()
+
class OpenidVerifyForm(forms.Form):
""" openid verify form (associate an openid with an account) """
- next = forms.CharField(max_length=255, widget = forms.HiddenInput(),
- required=False)
- username = forms.CharField(max_length=30,
- widget=forms.widgets.TextInput(attrs=attrs_dict))
+ next = NextUrlField()
+ username = UserNameField(must_exist=True)
password = forms.CharField(max_length=128,
widget=forms.widgets.PasswordInput(attrs=attrs_dict))
@@ -203,24 +279,6 @@ class OpenidVerifyForm(forms.Form):
prefix, initial)
self.user_cache = None
- def clean_username(self):
- """ validate username """
- if 'username' in self.cleaned_data:
- if not username_re.search(self.cleaned_data['username']):
- raise forms.ValidationError(_('invalid user name'))
- try:
- user = User.objects.get(
- username__exact = self.cleaned_data['username']
- )
- except User.DoesNotExist:
- raise forms.ValidationError(_("This username don't exist. \
- Please choose another."))
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(u'Somehow, that username is in \
- use for multiple accounts. Please contact us to get this \
- problem resolved.')
- return self.cleaned_data['username']
-
def clean_password(self):
""" test if password is valid for this user """
if 'username' in self.cleaned_data and \
@@ -236,7 +294,7 @@ class OpenidVerifyForm(forms.Form):
elif self.user_cache.is_active == False:
raise forms.ValidationError(_("This account is inactive."))
return self.cleaned_data['password']
-
+
def get_user(self):
""" get authenticated user """
return self.user_cache
@@ -245,88 +303,54 @@ class OpenidVerifyForm(forms.Form):
attrs_dict = { 'class': 'required' }
username_re = re.compile(r'^[\w ]+$')
-class RegistrationForm(forms.Form):
+class ClassicRegisterForm(forms.Form):
""" legacy registration form """
- next = forms.CharField(max_length=255, widget=forms.HiddenInput(),
- required=False)
- username = forms.CharField(max_length=30,
- widget=forms.TextInput(attrs=attrs_dict),
- label=_('choose a username'))
- email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
- maxlength=200)), label=_('your email address'))
+ next = NextUrlField()
+ username = UserNameField()
+ email = UserEmailField()
password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
- label=_('choose password'))
+ label=_('choose password'),
+ error_messages={'required':_('password is required')},
+ )
password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
- label=_('retype password'))
-
- def clean_username(self):
- """
- Validates that the username is alphanumeric and is not already
- in use.
-
- """
- if 'username' in self.cleaned_data:
- if not username_re.search(self.cleaned_data['username']):
- raise forms.ValidationError(u'Usernames can only contain \
- letters, numbers and underscores')
- try:
- user = User.objects.get(
- username__exact = self.cleaned_data['username']
- )
-
- except User.DoesNotExist:
- return self.cleaned_data['username']
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(u'Somehow, that username is in \
- use for multiple accounts. Please contact us to get this \
- problem resolved.')
- raise forms.ValidationError(u'This username is already taken. \
- Please choose another.')
-
- def clean_email(self):
- """ validate if email exist in database
- :return: raise error if it exist """
- if 'email' in self.cleaned_data:
- 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']
- #what if not?
+ label=mark_safe(_('retype password')),
+ error_messages={'required':_('please, retype your password'),
+ 'nomatch':_('sorry, entered passwords did not match, please try again')},
+ required=False
+ )
def clean_password2(self):
"""
Validates that the two password inputs match.
"""
- if 'password1' in self.cleaned_data and \
- 'password2' in self.cleaned_data and \
- self.cleaned_data['password1'] == \
+ self.cleaned_data['password2'] = self.cleaned_data.get('password2','')
+ if self.cleaned_data['password2'] == '':
+ del self.cleaned_data['password2']
+ raise forms.ValidationError(self.fields['password2'].error_messages['required'])
+ if 'password1' in self.cleaned_data \
+ and self.cleaned_data['password1'] == \
self.cleaned_data['password2']:
return self.cleaned_data['password2']
- raise forms.ValidationError(u'You must type the same password each \
- time')
+ else:
+ del self.cleaned_data['password2']
+ del self.cleaned_data['password1']
+ raise forms.ValidationError(self.fields['password2'].error_messages['nomatch'])
-
-class ChangepwForm(forms.Form):
+class ChangePasswordForm(forms.Form):
""" change password form """
- oldpw = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
- password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
- password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+ oldpw = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ label=mark_safe(_('Current password')))
+ password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ label=mark_safe(_('New password')))
+ password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ label=mark_safe(_('Retype new password')))
def __init__(self, data=None, user=None, *args, **kwargs):
if user is None:
raise TypeError("Keyword argument 'user' must be supplied")
- super(ChangepwForm, self).__init__(data, *args, **kwargs)
+ super(ChangePasswordForm, self).__init__(data, *args, **kwargs)
self.user = user
def clean_oldpw(self):
@@ -347,49 +371,35 @@ class ChangepwForm(forms.Form):
raise forms.ValidationError(_("new passwords do not match"))
-class ChangeemailForm(forms.Form):
+class ChangeEmailForm(forms.Form):
""" change email form """
- email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
- maxlength=200)), label=u'Email address')
- password = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+ email = UserEmailField(skip_clean=True)
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, \
initial=None, user=None):
- if user is None:
- raise TypeError("Keyword argument 'user' must be supplied")
- super(ChangeemailForm, self).__init__(data, files, auto_id,
+ super(ChangeEmailForm, self).__init__(data, files, auto_id,
prefix, initial)
- self.test_openid = False
self.user = user
-
-
+
def clean_email(self):
""" check if email don't exist """
if 'email' in self.cleaned_data:
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:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ if self.user and self.user == user:
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.')
+ 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):
- """ check if we have to test a legacy account or not """
- if 'password' in self.cleaned_data:
- if not self.user.check_password(self.cleaned_data['password']):
- self.test_openid = True
- return self.cleaned_data['password']
-
class ChangeopenidForm(forms.Form):
""" change openid form """
openid_url = forms.CharField(max_length=255,
@@ -422,8 +432,7 @@ class DeleteForm(forms.Form):
class EmailPasswordForm(forms.Form):
""" send new password form """
- username = forms.CharField(max_length=30,
- widget=forms.TextInput(attrs={'class': "required" }))
+ username = UserNameField(skip_clean=True,label=mark_safe(_('Your user name (<i>required</i>)')))
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None):
@@ -431,7 +440,6 @@ class EmailPasswordForm(forms.Form):
prefix, initial)
self.user_cache = None
-
def clean_username(self):
""" get user for this username """
if 'username' in self.cleaned_data:
diff --git a/django_authopenid/models.py b/django_authopenid/models.py
index e6fb8111..7b2e1c02 100644
--- a/django_authopenid/models.py
+++ b/django_authopenid/models.py
@@ -69,3 +69,8 @@ class UserPasswordQueue(models.Model):
def __unicode__(self):
return self.user.username
+
+class ExternalLoginData(models.Model):
+ external_username = models.CharField(max_length=40, unique=True, null=False)
+ external_session_data = models.TextField()
+ user = models.ForeignKey(User, null=True)
diff --git a/django_authopenid/urls.py b/django_authopenid/urls.py
index 3382434a..112cbbe1 100644
--- a/django_authopenid/urls.py
+++ b/django_authopenid/urls.py
@@ -7,11 +7,12 @@ 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%s$' % (_('signin/'),_('newquestion/')), 'signin', kwargs = {'newquestion':True}, name='user_signin_new_question'),
+ url(r'^%s%s$' % (_('signin/'),_('newanswer/')), 'signin', kwargs = {'newanswer':True}, name='user_signin_new_answer'),
url(r'^%s$' % _('signout/'), 'signout', name='user_signout'),
url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
name='user_complete_signin'),
+ url('^%s$' % _('external-login/'),'external_legacy_login_info', name='user_external_legacy_login_issues'),
url(r'^%s$' % _('register/'), 'register', name='user_register'),
url(r'^%s$' % _('signup/'), 'signup', name='user_signup'),
#disable current sendpw function
@@ -21,8 +22,10 @@ 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',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%s$' % (_('email/'),_('validate/')), 'changeemail', name='user_validateemail',kwargs = {'action':'validate'}),
+ url(r'^%s%s$' % (_('email/'), _('change/')), 'changeemail', name='user_changeemail'),
+ url(r'^%s%s$' % (_('email/'), _('sendkey/')), 'send_email_key', name='send_email_key'),
+ url(r'^%s%s(?P<id>\d+)/(?P<key>[\dabcdef]{32})/$' % (_('email/'), _('verify/')), 'verifyemail', name='user_verifyemail'),
+ url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'),
url(r'^%s$' % _('delete/'), 'delete', name='user_delete'),
)
diff --git a/django_authopenid/util.py b/django_authopenid/util.py
index 11afe53b..2b9d44a2 100644
--- a/django_authopenid/util.py
+++ b/django_authopenid/util.py
@@ -7,7 +7,7 @@ import openid.store
from django.db.models.query import Q
from django.conf import settings
from django.http import str_to_unicode
-
+from django.core.urlresolvers import reverse
# needed for some linux distributions like debian
try:
@@ -22,7 +22,7 @@ from models import Association, Nonce
__all__ = ['OpenID', 'DjangoOpenIDStore', 'from_openid_response', 'clean_next']
-DEFAULT_NEXT = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+DEFAULT_NEXT = '/' + getattr(settings, 'FORUM_SCRIPT_ALIAS')
def clean_next(next):
if next is None:
return DEFAULT_NEXT
@@ -32,6 +32,9 @@ def clean_next(next):
return next
return DEFAULT_NEXT
+def get_next_url(request):
+ return clean_next(request.REQUEST.get('next'))
+
class OpenID:
def __init__(self, openid_, issued, attrs=None, sreg_=None):
self.openid = openid_
diff --git a/django_authopenid/views.py b/django_authopenid/views.py
index fa94c4e5..feb6b58f 100644
--- a/django_authopenid/views.py
+++ b/django_authopenid/views.py
@@ -30,19 +30,20 @@
# (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, Http404
+from django.http import HttpResponseRedirect, get_host, Http404, \
+ HttpResponseServerError
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 logout #for login I've added wrapper below - called login
from django.contrib.auth.decorators import login_required
+from django.contrib.auth import authenticate
from django.core.urlresolvers import reverse
from django.utils.encoding import smart_unicode
from django.utils.html import escape
from django.utils.translation import ugettext as _
-from django.contrib.sites.models import Site
from django.utils.http import urlquote_plus
+from django.utils.safestring import mark_safe
from django.core.mail import send_mail
from django.views.defaults import server_error
@@ -60,15 +61,24 @@ import re
import urllib
-from django_authopenid.util import OpenID, DjangoOpenIDStore, from_openid_response, clean_next
-from django_authopenid.models import UserAssociation, UserPasswordQueue
-from django_authopenid.forms import OpenidSigninForm, OpenidAuthForm, OpenidRegisterForm, \
- OpenidVerifyForm, RegistrationForm, ChangepwForm, ChangeemailForm, \
+from forum.forms import EditUserEmailFeedsForm
+from django_authopenid.util import OpenID, DjangoOpenIDStore, from_openid_response, get_next_url
+from django_authopenid.models import UserAssociation, UserPasswordQueue, ExternalLoginData
+from django_authopenid.forms import OpenidSigninForm, ClassicLoginForm, OpenidRegisterForm, \
+ OpenidVerifyForm, ClassicRegisterForm, ChangePasswordForm, ChangeEmailForm, \
ChangeopenidForm, DeleteForm, EmailPasswordForm
+import external_login
+import logging
def login(request,user):
from django.contrib.auth import login as _login
from forum.models import user_logged_in #custom signal
+
+ print 'in login call'
+
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ external_login.login(request,user)
+
#1) get old session key
session_key = request.session.session_key
#2) login and get new session key
@@ -76,6 +86,12 @@ def 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 logout(request):
+ from django.contrib.auth import logout as _logout#for login I've added wrapper below - called login
+ _logout(request)
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ external_login.logout(request)
+
def get_url_host(request):
if request.is_secure():
protocol = 'https'
@@ -138,7 +154,7 @@ def complete(request, on_success=None, on_failure=None, return_to=None):
def default_on_success(request, identity_url, openid_response):
""" default action on openid signin success """
request.session['openid'] = from_openid_response(openid_response)
- return HttpResponseRedirect(clean_next(request.GET.get('next')))
+ return HttpResponseRedirect(get_next_url(request))
def default_on_failure(request, message):
""" default failure action on signin """
@@ -152,16 +168,15 @@ def not_authenticated(func):
he is already logged."""
def decorated(request, *args, **kwargs):
if request.user.is_authenticated():
- next = request.GET.get("next", "/")
- return HttpResponseRedirect(next)
+ return HttpResponseRedirect(get_next_url(request))
return func(request, *args, **kwargs)
return decorated
@not_authenticated
def signin(request,newquestion=False,newanswer=False):
"""
- signin page. It manage the legacy authentification (user/password)
- and authentification with openid.
+ signin page. It manages the legacy authentification (user/password)
+ and openid authentification
url: /signin/
@@ -169,27 +184,112 @@ def signin(request,newquestion=False,newanswer=False):
"""
request.encoding = 'UTF-8'
on_failure = signin_failure
- next = clean_next(request.GET.get('next'))
-
+ email_feeds_form = EditUserEmailFeedsForm()
+ next = get_next_url(request)
form_signin = OpenidSigninForm(initial={'next':next})
- form_auth = OpenidAuthForm(initial={'next':next})
+ form_auth = ClassicLoginForm(initial={'next':next})
if request.POST:
-
+ #'blogin' - password login
if 'blogin' in request.POST.keys():
- # perform normal django authentification
- form_auth = OpenidAuthForm(request.POST)
+ form_auth = ClassicLoginForm(request.POST)
if form_auth.is_valid():
- user_ = form_auth.get_user()
- login(request, user_)
- next = clean_next(form_auth.cleaned_data.get('next'))
- print 'next stop is "%s"' % next
- return HttpResponseRedirect(next)
+ #have login and password and need to login through external website
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ username = form_auth.cleaned_data['username']
+ password = form_auth.cleaned_data['password']
+ next = form_auth.cleaned_data['next']
+ if form_auth.get_user() == None:
+ #need to create internal user
+
+ #1) save login and password temporarily in session
+ request.session['external_username'] = username
+ request.session['external_password'] = password
+
+ #2) see if username clashes with some existing user
+ #if so, we have to prompt the user to pick a different name
+ username_taken = User.is_username_taken(username)
+ #try:
+ # User.objects.get(username=username)
+ # username_taken = True
+ #except User.DoesNotExist:
+ # username_taken = False
+
+ #3) try to extract user email from external service
+ email = external_login.get_email(username,password)
+
+ email_feeds_form = EditUserEmailFeedsForm()
+ form_data = {'username':username,'email':email,'next':next}
+ form = OpenidRegisterForm(initial=form_data)
+ template_data = {'form1':form,'username':username,\
+ 'email_feeds_form':email_feeds_form,\
+ 'provider':mark_safe(settings.EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME),\
+ 'login_type':'legacy',\
+ 'gravatar_faq_url':reverse('faq') + '#gravatar',\
+ 'external_login_name_is_taken':username_taken}
+ return render('authopenid/complete.html',template_data,\
+ context_instance=RequestContext(request))
+ else:
+ #user existed, external password is ok
+ user = form_auth.get_user()
+ login(request,user)
+ response = HttpResponseRedirect(get_next_url(request))
+ external_login.set_login_cookies(response,user)
+ return response
+ else:
+ #regular password authentication
+ user = form_auth.get_user()
+ login(request, user)
+ return HttpResponseRedirect(get_next_url(request))
+
+ elif 'bnewaccount' in request.POST.keys():
+ #register externally logged in password user with a new local account
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ form = OpenidRegisterForm(request.POST)
+ email_feeds_form = EditUserEmailFeedsForm(request.POST)
+ form1_is_valid = form.is_valid()
+ form2_is_valid = email_feeds_form.is_valid()
+ if form1_is_valid and form2_is_valid:
+ #create the user
+ username = form.cleaned_data['username']
+ password = request.session.get('external_password',None)
+ email = form.cleaned_data['email']
+ print 'got email addr %s' % email
+ if password and username:
+ User.objects.create_user(username,email,password)
+ user = authenticate(username=username,password=password)
+ external_username = request.session['external_username']
+ eld = ExternalLoginData.objects.get(external_username=external_username)
+ eld.user = user
+ eld.save()
+ login(request,user)
+ email_feeds_form.save(user)
+ del request.session['external_username']
+ del request.session['external_password']
+ return HttpResponseRedirect(reverse('index'))
+ else:
+ if password:
+ del request.session['external_username']
+ if username:
+ del request.session['external_password']
+ return HttpResponseServerError()
+ else:
+ username = request.POST.get('username',None)
+ provider = mark_safe(settings.EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME)
+ username_taken = User.is_username_taken(username)
+ data = {'login_type':'legacy','form1':form,'username':username,\
+ 'email_feeds_form':email_feeds_form,'provider':provider,\
+ 'gravatar_faq_url':reverse('faq') + '#gravatar',\
+ 'external_login_name_is_taken':username_taken}
+ return render('authopenid/complete.html',data,
+ context_instance=RequestContext(request))
+ else:
+ raise Http404
elif 'bsignin' in request.POST.keys() or 'openid_username' in request.POST.keys():
form_signin = OpenidSigninForm(request.POST)
if form_signin.is_valid():
- next = clean_next(form_signin.cleaned_data.get('next'))
+ next = form_signin.cleaned_data['next']
sreg_req = sreg.SRegRequest(optional=['nickname', 'email'])
redirect_to = "%s%s?%s" % (
get_url_host(request),
@@ -203,6 +303,7 @@ def signin(request,newquestion=False,newanswer=False):
sreg_request=sreg_req)
+ #if request is GET
question = None
if newquestion == True:
from forum.models import AnonymousQuestion as AQ
@@ -232,7 +333,6 @@ def complete_signin(request):
return complete(request, signin_success, signin_failure,
get_url_host(request) + reverse('user_complete_signin'))
-
def signin_success(request, identity_url, openid_response):
"""
openid signin success.
@@ -256,8 +356,7 @@ def signin_success(request, identity_url, openid_response):
user_.backend = "django.contrib.auth.backends.ModelBackend"
login(request, user_)
- next = clean_next(request.GET.get('next'))
- return HttpResponseRedirect(next)
+ return HttpResponseRedirect(get_next_url(request))
def is_association_exist(openid_url):
""" test if an openid is already in database """
@@ -284,15 +383,13 @@ def register(request):
template : authopenid/complete.html
"""
- is_redirect = False
- next = clean_next(request.GET.get('next'))
openid_ = request.session.get('openid', None)
+ next = get_next_url(request)
if not openid_:
- return HttpResponseRedirect(reverse('user_signin') + next)
+ return HttpResponseRedirect(reverse('user_signin') + '?next=%s' % next)
nickname = openid_.sreg.get('nickname', '')
email = openid_.sreg.get('email', '')
-
form1 = OpenidRegisterForm(initial={
'next': next,
'username': nickname,
@@ -302,18 +399,22 @@ def register(request):
'next': next,
'username': nickname,
})
+ email_feeds_form = EditUserEmailFeedsForm()
user_ = None
+ is_redirect = False
if request.POST:
- just_completed = False
if 'bnewaccount' in request.POST.keys():
form1 = OpenidRegisterForm(request.POST)
- if form1.is_valid():
- next = clean_next(form1.cleaned_data.get('next'))
+ email_feeds_form = EditUserEmailFeedsForm(request.POST)
+ if form1.is_valid() and email_feeds_form.is_valid():
+ next = form1.cleaned_data['next']
is_redirect = True
tmp_pwd = User.objects.make_random_password()
user_ = User.objects.create_user(form1.cleaned_data['username'],
form1.cleaned_data['email'], tmp_pwd)
+
+ user_.set_unusable_password()
# make association with openid
uassoc = UserAssociation(openid_url=str(openid_),
user_id=user_.id)
@@ -322,11 +423,12 @@ def register(request):
# login
user_.backend = "django.contrib.auth.backends.ModelBackend"
login(request, user_)
+ email_feeds_form.save(user_)
elif 'bverify' in request.POST.keys():
form2 = OpenidVerifyForm(request.POST)
if form2.is_valid():
is_redirect = True
- next = clean_next(form2.cleaned_data.get('next'))
+ next = form2.cleaned_data['next']
user_ = form2.get_user()
uassoc = UserAssociation(openid_url=str(openid_),
@@ -338,13 +440,16 @@ def register(request):
#this needs to be a function call becase this is also done
#if user just logged in and did not need to create the new account
- if user_ != None 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_ != None and user_.is_authenticated():
- return HttpResponseRedirect('/')
+ if user_ != None:
+ if 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
+ if user_.is_authenticated():
+ return HttpResponseRedirect(reverse('index'))
+ else:
+ raise Exception('openid login failed')#should not ever get here
openid_str = str(openid_)
bits = openid_str.split('/')
@@ -366,9 +471,12 @@ def register(request):
return render('authopenid/complete.html', {
'form1': form1,
'form2': form2,
- 'provider':provider_logo,
- 'nickname': nickname,
- 'email': email
+ 'email_feeds_form': email_feeds_form,
+ 'provider':mark_safe(provider_logo),
+ 'username': nickname,
+ 'email': email,
+ 'login_type':'openid',
+ 'gravatar_faq_url':reverse('faq') + '#gravatar',
}, context_instance=RequestContext(request))
def signin_failure(request, message):
@@ -377,9 +485,9 @@ def signin_failure(request, message):
template : "authopenid/signin.html"
"""
- next = clean_next(request.GET.get('next'))
+ next = get_next_url(request)
form_signin = OpenidSigninForm(initial={'next': next})
- form_auth = OpenidAuthForm(initial={'next': next})
+ form_auth = ClassicLoginForm(initial={'next': next})
return render('authopenid/signin.html', {
'msg': message,
@@ -396,42 +504,52 @@ def signup(request):
templates: authopenid/signup.html, authopenid/confirm_email.txt
"""
- action_signin = reverse('user_signin')
- next = clean_next(request.GET.get('next'))
- form = RegistrationForm(initial={'next':next})
- form_signin = OpenidSigninForm(initial={'next':next})
-
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
+ next = get_next_url(request)
if request.POST:
- form = RegistrationForm(request.POST)
- if form.is_valid():
- next = clean_next(form.cleaned_data.get('next'))
- user_ = User.objects.create_user( form.cleaned_data['username'],
- form.cleaned_data['email'], form.cleaned_data['password1'])
+ form = ClassicRegisterForm(request.POST)
+ email_feeds_form = EditUserEmailFeedsForm(request.POST)
+
+ #validation outside if to remember form values
+ form1_is_valid = form.is_valid()
+ form2_is_valid = email_feeds_form.is_valid()
+ if form1_is_valid and form2_is_valid:
+ next = form.cleaned_data['next']
+ username = form.cleaned_data['username']
+ password = form.cleaned_data['password1']
+ email = form.cleaned_data['email']
+
+ user_ = User.objects.create_user( username,email,password )
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ external_login.create_user(username,email,password)
user_.backend = "django.contrib.auth.backends.ModelBackend"
login(request, user_)
+ email_feeds_form.save(user_)
# send email
- current_domain = Site.objects.get_current().domain
- subject = _("Welcome")
+ subject = _("Welcome email subject line")
message_template = loader.get_template(
'authopenid/confirm_email.txt'
)
message_context = Context({
- 'site_url': 'http://%s/' % current_domain,
- 'username': form.cleaned_data['username'],
- 'password': form.cleaned_data['password1']
+ 'signup_url': settings.APP_URL + reverse('user_signin'),
+ 'username': username,
+ 'password': password,
})
message = message_template.render(message_context)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
[user_.email])
-
return HttpResponseRedirect(next)
-
+ else:
+ form = ClassicRegisterForm(initial={'next':next})
+ email_feeds_form = EditUserEmailFeedsForm()
return render('authopenid/signup.html', {
- 'form': form,
- 'form2': form_signin,
+ 'form': form,
+ 'email_feeds_form': email_feeds_form
}, context_instance=RequestContext(request))
+ #what if request is not posted?
@login_required
def signout(request):
@@ -444,10 +562,8 @@ def signout(request):
del request.session['openid']
except KeyError:
pass
- next = clean_next(request.GET.get('next'))
logout(request)
-
- return HttpResponseRedirect(next)
+ return HttpResponseRedirect(get_next_url(request))
def xrdf(request):
url_host = get_url_host(request)
@@ -495,11 +611,16 @@ def changepw(request):
url : /changepw/
template: authopenid/changepw.html
"""
-
user_ = request.user
+
+ if user_.has_usable_password():
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
+ else:
+ raise Http404
if request.POST:
- form = ChangepwForm(request.POST, user=user_)
+ form = ChangePasswordForm(request.POST, user=user_)
if form.is_valid():
user_.set_password(form.cleaned_data['password1'])
user_.save()
@@ -509,18 +630,20 @@ def changepw(request):
urlquote_plus(msg))
return HttpResponseRedirect(redirect)
else:
- form = ChangepwForm(user=user_)
+ form = ChangePasswordForm(user=user_)
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')
+ msg_text = _('your email needs to be validated see %(details_url)s') \
+ % {'details_url':reverse('faq') + '#validate'}
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')
+ msg_text = _('your email needs to be validated see %(details_url)s') \
+ % {'details_url':reverse('faq') + '#validate'}
if len(messages) == 0:
user.message_set.create(message=msg_text)
@@ -540,11 +663,11 @@ def _send_email_key(user):
"""private function. sends email containing validation key
to user's email address
"""
- subject = _("Welcome")
+ subject = _("Email verification subject line")
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)
+ 'validation_link': settings.APP_URL + reverse('user_verifyemail', kwargs={'id':user.id,'key':user.email_key})
})
message = message_template.render(message_context)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])
@@ -580,7 +703,7 @@ def send_email_key(request):
context_instance=RequestContext(request)
)
else:
- _send_email_key(request.user)
+ send_new_email_key(request.user)
return validation_email_sent(request)
else:
raise Http404
@@ -589,10 +712,11 @@ def send_email_key(request):
#internal server view used as return value by other views
def validation_email_sent(request):
return render('authopenid/changeemail.html',
- { 'email': request.user.email, 'action_type': 'validate', },
+ { 'email': request.user.email,
+ 'change_email_url': reverse('user_changeemail'),
+ 'action_type': 'validate', },
context_instance=RequestContext(request))
-
def verifyemail(request,id=None,key=None):
"""
view that is shown when user clicks email validation link
@@ -611,53 +735,49 @@ def verifyemail(request,id=None,key=None):
raise Http404
@login_required
-def changeemail(request):
+def changeemail(request, action='change'):
"""
- changeemail view. It require password or openid to allow change.
+ changeemail view. requires openid with request type GET
url: /email/*
template : authopenid/changeemail.html
"""
- msg = request.GET.get('msg', '')
+ msg = request.GET.get('msg', None)
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 'cancel' in request.POST:
+ msg = _('your email was not changed')
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect(get_next_url(request))
+ form = ChangeEmailForm(request.POST, user=user_)
if form.is_valid():
- if not form.test_openid:
- 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)
+ new_email = form.cleaned_data['email']
+ if new_email != user_.email:
+ if settings.EMAIL_VALIDATION == 'on':
+ action = 'validate'
else:
- action = 'keep'
+ action = 'done_novalidate'
+ set_new_email(user_, new_email,nomessage=True)
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)
+ action = 'keep'
elif not request.POST and 'openid.mode' in request.GET:
+ redirect_to = get_url_host(request) + reverse('user_changeemail')
return complete(request, emailopenid_success,
emailopenid_failure, redirect_to)
else:
- form = ChangeemailForm(initial={'email': user_.email},
+ form = ChangeEmailForm(initial={'email': user_.email},
user=user_)
-
output = render('authopenid/changeemail.html', {
'form': form,
'email': user_.email,
'action_type': action,
+ 'gravatar_faq_url': reverse('faq') + '#gravatar',
+ 'change_email_url': reverse('user_changeemail'),
'msg': msg
}, context_instance=RequestContext(request))
@@ -666,7 +786,6 @@ def changeemail(request):
return output
-
def emailopenid_success(request, identity_url, openid_response):
openid_ = from_openid_response(openid_response)
@@ -840,7 +959,7 @@ def deleteopenid_success(request, identity_url, openid_response):
identity_url))
msg = _("Account deleted.")
- redirect = "/?msg=%s" % (urlquote_plus(msg))
+ redirect = reverse('index') + u"/?msg=%s" % (urlquote_plus(msg))
return HttpResponseRedirect(redirect)
@@ -848,6 +967,8 @@ def deleteopenid_failure(request, message):
redirect_to = "%s?msg=%s" % (reverse('user_delete'), urlquote_plus(message))
return HttpResponseRedirect(redirect_to)
+def external_legacy_login_info(request):
+ return render('authopenid/external_legacy_login_info.html', context_instance=RequestContext(request))
def sendpw(request):
"""
@@ -859,6 +980,8 @@ def sendpw(request):
templates : authopenid/sendpw_email.txt, authopenid/sendpw.html
"""
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
msg = request.GET.get('msg','')
if request.POST:
@@ -878,21 +1001,20 @@ def sendpw(request):
uqueue.confirm_key = confirm_key
uqueue.save()
# send email
- current_domain = Site.objects.get_current().domain
subject = _("Request for new password")
message_template = loader.get_template(
'authopenid/sendpw_email.txt')
+ key_link = settings.APP_URL + reverse('user_confirmchangepw') + '?key=' + confirm_key
message_context = Context({
- 'site_url': 'http://%s' % current_domain,
- 'confirm_key': confirm_key,
+ 'site_url': settings.APP_URL + reverse('index'),
+ 'key_link': key_link,
'username': form.user_cache.username,
'password': new_pw,
- 'url_confirm': reverse('user_confirmchangepw'),
})
message = message_template.render(message_context)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
[form.user_cache.email])
- msg = _("A new password has been sent to your email address.")
+ msg = _("A new password and the activation link were sent to your email address.")
else:
form = EmailPasswordForm()
@@ -915,7 +1037,7 @@ def confirmchangepw(request):
"""
confirm_key = request.GET.get('key', '')
if not confirm_key:
- return HttpResponseRedirect('/')
+ return HttpResponseRedirect(reverse('index'))
try:
uqueue = UserPasswordQueue.objects.get(
diff --git a/drop-all-tables.sh b/drop-all-tables.sh
new file mode 100644
index 00000000..1e55cb1f
--- /dev/null
+++ b/drop-all-tables.sh
@@ -0,0 +1,4 @@
+mysql_username=''
+mysql_database=''
+mysqldump -u $mysql_username -p --add-drop-table --no-data $mysql_database | grep ^DROP
+#| mysql -u[USERNAME] -p[PASSWORD] [DATABASE]
diff --git a/drop-all.sql b/drop-all.sql
new file mode 100644
index 00000000..52feb337
--- /dev/null
+++ b/drop-all.sql
@@ -0,0 +1,39 @@
+DROP TABLE IF EXISTS `activity`;
+DROP TABLE IF EXISTS `answer`;
+DROP TABLE IF EXISTS `answer_revision`;
+DROP TABLE IF EXISTS `auth_group`;
+DROP TABLE IF EXISTS `auth_group_permissions`;
+DROP TABLE IF EXISTS `auth_message`;
+DROP TABLE IF EXISTS `auth_permission`;
+DROP TABLE IF EXISTS `auth_user`;
+DROP TABLE IF EXISTS `auth_user_groups`;
+DROP TABLE IF EXISTS `auth_user_user_permissions`;
+DROP TABLE IF EXISTS `award`;
+DROP TABLE IF EXISTS `badge`;
+DROP TABLE IF EXISTS `book`;
+DROP TABLE IF EXISTS `book_author_info`;
+DROP TABLE IF EXISTS `book_author_rss`;
+DROP TABLE IF EXISTS `book_question`;
+DROP TABLE IF EXISTS `comment`;
+DROP TABLE IF EXISTS `django_admin_log`;
+DROP TABLE IF EXISTS `django_authopenid_association`;
+DROP TABLE IF EXISTS `django_authopenid_externallogindata`;
+DROP TABLE IF EXISTS `django_authopenid_nonce`;
+DROP TABLE IF EXISTS `django_authopenid_userassociation`;
+DROP TABLE IF EXISTS `django_authopenid_userpasswordqueue`;
+DROP TABLE IF EXISTS `django_content_type`;
+DROP TABLE IF EXISTS `django_session`;
+DROP TABLE IF EXISTS `django_site`;
+DROP TABLE IF EXISTS `favorite_question`;
+DROP TABLE IF EXISTS `flagged_item`;
+DROP TABLE IF EXISTS `forum_anonymousanswer`;
+DROP TABLE IF EXISTS `forum_anonymousemail`;
+DROP TABLE IF EXISTS `forum_anonymousquestion`;
+DROP TABLE IF EXISTS `forum_emailfeed`;
+DROP TABLE IF EXISTS `forum_emailfeedsetting`;
+DROP TABLE IF EXISTS `question`;
+DROP TABLE IF EXISTS `question_revision`;
+DROP TABLE IF EXISTS `question_tags`;
+DROP TABLE IF EXISTS `repute`;
+DROP TABLE IF EXISTS `tag`;
+DROP TABLE IF EXISTS `vote`;
diff --git a/forum/auth.py b/forum/auth.py
index 776746e8..eb81f853 100644
--- a/forum/auth.py
+++ b/forum/auth.py
@@ -1,4 +1,4 @@
-"""
+ """
Authorisation related functions.
The actions a User is authorised to perform are dependent on their reputation
@@ -6,18 +6,20 @@ and superuser status.
"""
import datetime
from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import ugettext as _
from django.db import transaction
from models import Repute
from models import Question
from models import Answer
from const import TYPE_REPUTATION
+import logging
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
+LEAVE_COMMENTS = 50
UPLOAD_FILES = 60
VOTE_DOWN = 100
CLOSE_OWN_QUESTIONS = 250
@@ -58,6 +60,9 @@ REPUTATION_RULES = {
'lose_by_upvote_canceled' : -10,
}
+def can_moderate_users(user):
+ return user.is_superuser
+
def can_vote_up(user):
"""Determines if a User can vote Questions and Answers up."""
return user.is_authenticated() and (
@@ -70,11 +75,18 @@ def can_flag_offensive(user):
user.reputation >= FLAG_OFFENSIVE or
user.is_superuser)
-def can_add_comments(user):
+def can_add_comments(user,subject):
"""Determines if a User can add comments to Questions and Answers."""
- return user.is_authenticated() and (
- user.reputation >= LEAVE_COMMENTS or
- user.is_superuser)
+ if user.is_authenticated():
+ if user.id == subject.author.id:
+ return True
+ if user.reputation >= LEAVE_COMMENTS:
+ return True
+ if user.is_superuser:
+ return True
+ if isinstance(subject,Answer) and subject.question.author.id == user.id:
+ return True
+ return False
def can_vote_down(user):
"""Determines if a User can vote Questions and Answers down."""
@@ -139,8 +151,21 @@ def can_reopen_question(user, question):
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
+ if user.is_superuser:
+ return True
+ elif user.is_authenticated() and user == post.author:
+ if isinstance(post,Answer):
+ return True
+ elif isinstance(post,Question):
+ answers = post.answers.all()
+ for answer in answers:
+ if user != answer.author and answer.deleted == False:
+ return False
+ return True
+ else:
+ return False
+ else:
+ return False
def can_view_deleted_post(user, post):
return user.is_superuser
@@ -422,15 +447,20 @@ def onDownVotedCanceled(vote, post, user):
def onDeleteCanceled(post, user):
post.deleted = False
- post.deleted_by = None
- post.deleted_at = None
+ 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()
+ logging.debug('now restoring something')
+ if isinstance(post,Answer):
+ logging.debug('updated answer count on undelete, have %d' % post.question.answer_count)
+ Question.objects.update_answer_count(post.question)
+ elif isinstance(post,Question):
+ 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
@@ -438,9 +468,31 @@ def onDeleted(post, 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()
+ if isinstance(post, Question):
+ 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()
+ else:
+ tag.used_count = tag.used_count - 1
tag.save()
+
+ answers = post.answers.all()
+ if user == post.author:
+ if len(answers) > 0:
+ msg = _('Your question and all of it\'s answers have been deleted')
+ else:
+ msg = _('Your question has been deleted')
+ else:
+ if len(answers) > 0:
+ msg = _('The question and all of it\'s answers have been deleted')
+ else:
+ msg = _('The question has been deleted')
+ user.message_set.create(message=msg)
+ logging.debug('posted a message %s' % msg)
+ for answer in answers:
+ onDeleted(answer, user)
+ elif isinstance(post, Answer):
+ Question.objects.update_answer_count(post.question)
+ logging.debug('updated answer count to %d' % post.question.answer_count)
diff --git a/forum/const.py b/forum/const.py
index f6649cc4..76fd4a24 100644
--- a/forum/const.py
+++ b/forum/const.py
@@ -6,7 +6,7 @@ For reasons that models, views can't have unicode text in this project, all unic
"""
CLOSE_REASONS = (
(1, _('duplicate question')),
- (2, _('question if off-topic or not relevant')),
+ (2, _('question is off-topic or not relevant')),
(3, _('too subjective and argumentative')),
(4, _('is not an answer to the question')),
(5, _('the question is answered, right answer was accepted')),
@@ -49,6 +49,7 @@ TYPE_ACTIVITY_MARK_OFFENSIVE=14
TYPE_ACTIVITY_UPDATE_TAGS=15
TYPE_ACTIVITY_FAVORITE=16
TYPE_ACTIVITY_USER_FULL_UPDATED = 17
+TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT = 18
#TYPE_ACTIVITY_EDIT_QUESTION=17
#TYPE_ACTIVITY_EDIT_ANSWER=18
@@ -70,6 +71,7 @@ TYPE_ACTIVITY = (
(TYPE_ACTIVITY_UPDATE_TAGS, _('updated tags')),
(TYPE_ACTIVITY_FAVORITE, _('selected favorite')),
(TYPE_ACTIVITY_USER_FULL_UPDATED, _('completed user profile')),
+ (TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT, _('email update sent to user')),
)
TYPE_RESPONSE = {
@@ -85,3 +87,6 @@ CONST = {
'default_version' : _('initial version'),
'retagged' : _('retagged'),
}
+
+#how to filter questions by tags in email digests?
+TAG_EMAIL_FILTER_CHOICES = (('ignored', _('exclude ignored tags')),('interesting',_('allow only selected tags')))
diff --git a/forum/feed.py b/forum/feed.py
index 22a075a5..59983161 100644
--- a/forum/feed.py
+++ b/forum/feed.py
@@ -16,7 +16,7 @@ from models import Question
import settings
class RssLastestQuestionsFeed(Feed):
title = settings.APP_TITLE + _(' - ')+ _('latest questions')
- link = settings.APP_URL + '/' + _('questions/')
+ link = settings.APP_URL + '/' + _('question/')
description = settings.APP_DESCRIPTION
#ttl = 10
copyright = settings.APP_COPYRIGHT
@@ -34,7 +34,7 @@ class RssLastestQuestionsFeed(Feed):
return item.added_at
def items(self, item):
- return Question.objects.filter(deleted=False).order_by('-added_at')[:30]
+ return Question.objects.filter(deleted=False).order_by('-last_activity_at')[:30]
def main():
pass
diff --git a/forum/forms.py b/forum/forms.py
index 98ae3cbb..ad8a676a 100644
--- a/forum/forms.py
+++ b/forum/forms.py
@@ -4,6 +4,8 @@ from django import forms
from models import *
from const import *
from django.utils.translation import ugettext as _
+from django_authopenid.forms import NextUrlField, UserNameField
+import settings
class TitleField(forms.CharField):
def __init__(self, *args, **kwargs):
@@ -47,26 +49,28 @@ class TagNamesField(forms.CharField):
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)
+ def clean(self, value):
+ value = super(TagNamesField, self).clean(value)
+ data = value.strip()
+ if len(data) < 1:
+ raise forms.ValidationError(_('tags are required'))
+
+ split_re = re.compile(r'[ ,]+')
+ list = split_re.split(data)
+ list_temp = []
+ if len(list) > 5:
+ raise forms.ValidationError(_('please use 5 tags or less'))
+ for tag in list:
+ if len(tag) > 20:
+ raise forms.ValidationError(_('tags must be shorter than 20 characters'))
+ #take tag regex from settings
+ tagname_re = re.compile(r'[a-z0-9]+')
+ if not tagname_re.match(tag):
+ raise forms.ValidationError(_('please use following characters in tags: letters \'a-z\', numbers, and characters \'.-_#\''))
+ # only keep one same tag
+ if tag not in list_temp and len(tag.strip()) > 0:
+ list_temp.append(tag)
+ return u' '.join(list_temp)
class WikiField(forms.BooleanField):
def __init__(self, *args, **kwargs):
@@ -74,11 +78,14 @@ class WikiField(forms.BooleanField):
self.required = False
self.label = _('community wiki')
self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown')
+ def clean(self,value):
+ return value and settings.WIKI_ON
class EmailNotifyField(forms.BooleanField):
def __init__(self, *args, **kwargs):
super(EmailNotifyField, self).__init__(*args, **kwargs)
self.required = False
+ self.widget.attrs['class'] = 'nomargin'
class SummaryField(forms.CharField):
def __init__(self, *args, **kwargs):
@@ -89,6 +96,25 @@ class SummaryField(forms.CharField):
self.label = _('update summary:')
self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)')
+class ModerateUserForm(forms.ModelForm):
+ is_approved = forms.BooleanField(label=_("Automatically accept user's contributions for the email updates"),
+ required=False)
+
+ def clean_is_approved(self):
+ if 'is_approved' not in self.cleaned_data:
+ self.cleaned_data['is_approved'] = False
+ return self.cleaned_data['is_approved']
+
+ class Meta:
+ model = User
+ fields = ('is_approved',)
+
+class FeedbackForm(forms.Form):
+ name = forms.CharField(label=_('Your name:'), required=False)
+ email = forms.EmailField(label=_('Email (not shared with anyone):'), required=False)
+ message = forms.CharField(label=_('Your message:'), max_length=800,widget=forms.Textarea(attrs={'cols':60}))
+ next = NextUrlField()
+
class AskForm(forms.Form):
title = TitleField()
text = EditorField()
@@ -109,16 +135,12 @@ class AnswerForm(forms.Form):
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:
+ if question.wiki and settings.WIKI_ON:
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
+ if user in question.followed_by.all():
+ self.fields['email_notify'].initial = True
+ return
self.fields['email_notify'].initial = False
@@ -174,6 +196,7 @@ class EditAnswerForm(forms.Form):
class EditUserForm(forms.Form):
email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ username = UserNameField(label=_('Screen name'))
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}))
@@ -182,6 +205,7 @@ class EditUserForm(forms.Form):
def __init__(self, user, *args, **kwargs):
super(EditUserForm, self).__init__(*args, **kwargs)
+ self.fields['username'].initial = user.username
self.fields['email'].initial = user.email
self.fields['realname'].initial = user.real_name
self.fields['website'].initial = user.website
@@ -208,3 +232,87 @@ class EditUserForm(forms.Form):
raise forms.ValidationError(_('this email has already been registered, please use another one'))
raise forms.ValidationError(_('this email has already been registered, please use another one'))
return self.cleaned_data['email']
+
+class TagFilterSelectionForm(forms.ModelForm):
+ tag_filter_setting = forms.ChoiceField(choices=TAG_EMAIL_FILTER_CHOICES, #imported from forum/const.py
+ initial='ignored',
+ label=_('Choose email tag filter'),
+ widget=forms.RadioSelect)
+ class Meta:
+ model = User
+ fields = ('tag_filter_setting',)
+
+ def save(self):
+ before = self.instance.tag_filter_setting
+ super(TagFilterSelectionForm, self).save()
+ after = self.instance.tag_filter_setting #User.objects.get(pk=self.instance.id).tag_filter_setting
+ if before != after:
+ return True
+ return False
+
+class EditUserEmailFeedsForm(forms.Form):
+ WN = (('w',_('weekly')),('n',_('no email')))
+ DWN = (('d',_('daily')),('w',_('weekly')),('n',_('no email')))
+ FORM_TO_MODEL_MAP = {
+ 'all_questions':'q_all',
+ 'asked_by_me':'q_ask',
+ 'answered_by_me':'q_ans',
+ 'individually_selected':'q_sel',
+ }
+ NO_EMAIL_INITIAL = {
+ 'all_questions':'n',
+ 'asked_by_me':'n',
+ 'answered_by_me':'n',
+ 'individually_selected':'n',
+ }
+ asked_by_me = forms.ChoiceField(choices=DWN,initial='w',
+ widget=forms.RadioSelect,
+ label=_('Asked by me'))
+ answered_by_me = forms.ChoiceField(choices=DWN,initial='w',
+ widget=forms.RadioSelect,
+ label=_('Answered by me'))
+ individually_selected = forms.ChoiceField(choices=DWN,initial='w',
+ widget=forms.RadioSelect,
+ label=_('Individually selected'))
+ all_questions = forms.ChoiceField(choices=DWN,initial='w',
+ widget=forms.RadioSelect,
+ label=_('Entire forum (tag filtered)'),)
+
+ def set_initial_values(self,user=None):
+ KEY_MAP = dict([(v,k) for k,v in self.FORM_TO_MODEL_MAP.iteritems()])
+ if user != None:
+ settings = EmailFeedSetting.objects.filter(subscriber=user)
+ initial_values = {}
+ for setting in settings:
+ feed_type = setting.feed_type
+ form_field = KEY_MAP[feed_type]
+ frequency = setting.frequency
+ initial_values[form_field] = frequency
+ self.initial = initial_values
+ return self
+
+ def reset(self):
+ self.cleaned_data['all_questions'] = 'n'
+ self.cleaned_data['asked_by_me'] = 'n'
+ self.cleaned_data['answered_by_me'] = 'n'
+ self.cleaned_data['individually_selected'] = 'n'
+ self.initial = self.NO_EMAIL_INITIAL
+ return self
+
+ def save(self,user):
+ changed = False
+ for form_field, feed_type in self.FORM_TO_MODEL_MAP.items():
+ s, created = EmailFeedSetting.objects.get_or_create(subscriber=user,\
+ feed_type=feed_type)
+ new_value = self.cleaned_data[form_field]
+ if s.frequency != new_value:
+ s.frequency = self.cleaned_data[form_field]
+ s.save()
+ changed = True
+ else:
+ if created:
+ s.save()
+ if form_field == 'individually_selected':
+ feed_type = ContentType.objects.get_for_model(Question)
+ user.followed_questions.clear()
+ return changed
diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py
index 3c37aaa3..283d5683 100644
--- a/forum/management/commands/send_email_alerts.py
+++ b/forum/management/commands/send_email_alerts.py
@@ -1,10 +1,21 @@
from django.core.management.base import NoArgsCommand
from django.db import connection
+from django.db.models import Q, F
from forum.models import *
-import collections
+<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
+=======
+from forum import const
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
from django.core.mail import EmailMessage
from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+import datetime
import settings
+<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
+=======
+import logging
+from utils.odict import OrderedDict
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
class Command(NoArgsCommand):
def handle_noargs(self,**options):
@@ -15,27 +26,257 @@ class Command(NoArgsCommand):
finally:
connection.close()
+ def get_updated_questions_for_user(self,user):
+<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
+ q_sel = []
+ q_ask = []
+ q_ans = []
+ q_all = []
+=======
+ q_sel = None
+ q_ask = None
+ q_ans = None
+ q_all = None
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
+ now = datetime.datetime.now()
+ Q_set1 = Question.objects.exclude(
+ last_activity_by=user,
+ ).exclude(
+ last_activity_at__lt=user.date_joined
+ ).filter(
+ Q(viewed__who=user,viewed__when__lt=F('last_activity_at')) | \
+ ~Q(viewed__who=user)
+ ).exclude(
+ deleted=True
+ ).exclude(
+ closed=True
+ )
+<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
+=======
+
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
+ user_feeds = EmailFeedSetting.objects.filter(subscriber=user).exclude(frequency='n')
+ for feed in user_feeds:
+ cutoff_time = now - EmailFeedSetting.DELTA_TABLE[feed.frequency]
+ if feed.reported_at == None or feed.reported_at <= cutoff_time:
+<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
+ Q_set = Q_set1.exclude(last_activity_at__gt=cutoff_time)
+=======
+ Q_set = Q_set1.exclude(last_activity_at__gt=cutoff_time)#report these excluded later
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
+ feed.reported_at = now
+ feed.save()#may not actually report anything, depending on filters below
+ if feed.feed_type == 'q_sel':
+ q_sel = Q_set.filter(followed_by=user)
+<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
+ q_sel.cutoff_time = cutoff_time
+=======
+ q_sel.cutoff_time = cutoff_time #store cutoff time per query set
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
+ elif feed.feed_type == 'q_ask':
+ q_ask = Q_set.filter(author=user)
+ q_ask.cutoff_time = cutoff_time
+ elif feed.feed_type == 'q_ans':
+ q_ans = Q_set.filter(answers__author=user)
+ q_ans.cutoff_time = cutoff_time
+ elif feed.feed_type == 'q_all':
+<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
+ q_all = Q_set
+ q_all.cutoff_time = cutoff_time
+ #build list in this order
+ q_tbl = {}
+ def extend_question_list(src, dst):
+ if isinstance(src,list):
+ return
+ cutoff_time = src.cutoff_time
+ for q in src:
+ if q in dst:
+ if cutoff_time < dst[q]:
+ dst[q] = cutoff_time
+ else:
+ dst[q] = cutoff_time
+
+ extend_question_list(q_sel, q_tbl)
+ extend_question_list(q_ask, q_tbl)
+ extend_question_list(q_ans, q_tbl)
+ extend_question_list(q_all, q_tbl)
+
+ ctype = ContentType.objects.get_for_model(Question)
+ out = {}
+ for q, cutoff_time in q_tbl.items():
+ #todo use Activity, but first start keeping more Activity records
+ #act = Activity.objects.filter(content_type=ctype, object_id=q.id)
+ #get info on question edits, answer edits, comments
+ out[q] = {}
+ q_rev = QuestionRevision.objects.filter(question=q,revised_at__lt=cutoff_time)
+ q_rev = q_rev.exclude(author=user)
+ out[q]['q_rev'] = len(q_rev)
+ if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at:
+ out[q]['q_rev'] = 0
+ out[q]['new_q'] = True
+ else:
+ out[q]['new_q'] = False
+
+ new_ans = Answer.objects.filter(question=q,added_at__lt=cutoff_time)
+ new_ans = new_ans.exclude(author=user)
+ out[q]['new_ans'] = len(new_ans)
+ ans_rev = AnswerRevision.objects.filter(answer__question=q,revised_at__lt=cutoff_time)
+ ans_rev = ans_rev.exclude(author=user)
+ out[q]['ans_rev'] = len(ans_rev)
+ return out
+
+ def __act_count(self,string,number,output):
+=======
+ if user.tag_filter_setting == 'ignored':
+ ignored_tags = Tag.objects.filter(user_selections___reason='bad',user_selections__user=user)
+ q_all = Q_set.exclude( tags__in=ignored_tags )
+ else:
+ selected_tags = Tag.objects.filter(user_selections___reason='good',user_selections__user=user)
+ q_all = Q_set.filter( tags__in=selected_tags )
+ q_all.cutoff_time = cutoff_time
+ #build list in this order
+ q_list = OrderedDict()
+ def extend_question_list(src, dst):
+ """src is a query set with questions
+ or an empty list
+ dst - is an ordered dictionary
+ """
+ if src is None:
+ return #will not do anything if subscription of this type is not used
+ cutoff_time = src.cutoff_time
+ for q in src:
+ if q in dst:
+ if cutoff_time < dst[q]['cutoff_time']:
+ dst[q]['cutoff_time'] = cutoff_time
+ else:
+ #initialise a questions metadata dictionary to use for email reporting
+ dst[q] = {'cutoff_time':cutoff_time}
+
+ extend_question_list(q_sel, q_list)
+ extend_question_list(q_ask, q_list)
+ extend_question_list(q_ans, q_list)
+ extend_question_list(q_all, q_list)
+
+ ctype = ContentType.objects.get_for_model(Question)
+ EMAIL_UPDATE_ACTIVITY = const.TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT
+ for q, meta_data in q_list.items():
+ #todo use Activity, but first start keeping more Activity records
+ #act = Activity.objects.filter(content_type=ctype, object_id=q.id)
+ #because currently activity is not fully recorded to through
+ #revision records to see what kind modifications were done on
+ #the questions and answers
+ try:
+ update_info = Activity.objects.get(content_type=ctype,
+ object_id=q.id,
+ activity_type=EMAIL_UPDATE_ACTIVITY)
+ emailed_at = update_info.active_at
+ except Activity.DoesNotExist:
+ update_info = Activity(user=user, content_object=q, activity_type=EMAIL_UPDATE_ACTIVITY)
+ emailed_at = datetime.datetime(1970,1,1)#long time ago
+ except Activity.MultipleObjectsReturned:
+ raise Exception('server error - multiple question email activities found per user-question pair')
+
+ q_rev = QuestionRevision.objects.filter(question=q,\
+ revised_at__lt=cutoff_time,\
+ revised_at__gt=emailed_at)
+ q_rev = q_rev.exclude(author=user)
+ meta_data['q_rev'] = len(q_rev)
+ if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at:
+ meta_data['q_rev'] = 0
+ meta_data['new_q'] = True
+ else:
+ meta_data['new_q'] = False
+
+ new_ans = Answer.objects.filter(question=q,\
+ added_at__lt=cutoff_time,\
+ added_at__gt=emailed_at)
+ new_ans = new_ans.exclude(author=user)
+ meta_data['new_ans'] = len(new_ans)
+ ans_rev = AnswerRevision.objects.filter(answer__question=q,\
+ revised_at__lt=cutoff_time,\
+ revised_at__gt=emailed_at)
+ ans_rev = ans_rev.exclude(author=user)
+ meta_data['ans_rev'] = len(ans_rev)
+ if len(q_rev) == 0 and len(new_ans) == 0 and len(ans_rev) == 0:
+ meta_data['nothing_new'] = True
+ else:
+ meta_data['nothing_new'] = False
+ update_info.active_at = now
+ update_info.save() #save question email update activity
+ return q_list
+
+ def __action_count(self,string,number,output):
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
+ if number > 0:
+ output.append(_(string) % {'num':number})
+
def send_email_alerts(self):
- 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()
-
+<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
+ for user in User.objects.all():
+ q_list = self.get_updated_questions_for_user(user)
+ num_q = len(q_list)
+=======
+ #todo: move this to template
+ for user in User.objects.all():
+ q_list = self.get_updated_questions_for_user(user)
+ num_q = 0
+ num_moot = 0
+ for meta_data in q_list.values():
+ if meta_data['nothing_new'] == False:
+ num_q += 1
+ else:
+ num_moot += 1
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
+ if num_q > 0:
+ url_prefix = settings.APP_URL
+ subject = _('email update message subject')
+ text = ungettext('%(name)s, this is an update message header for a question',
+ '%(name)s, this is an update message header for %(num)d questions',num_q) \
+ % {'num':num_q, 'name':user.username}
+
+ text += '<ul>'
+<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
+ for q, act in q_list.items():
+ act_list = []
+ if act['new_q']:
+ act_list.append(_('new question'))
+ self.__act_count('%(num)d rev', act['q_rev'],act_list)
+ self.__act_count('%(num)d ans', act['new_ans'],act_list)
+ self.__act_count('%(num)d ans rev',act['ans_rev'],act_list)
+ act_token = ', '.join(act_list)
+ text += '<li><a href="%s?sort=latest">%s</a> <font color="#777777">(%s)</font></li>' \
+ % (url_prefix + q.get_absolute_url(), q.title, act_token)
+ text += '</ul>'
+=======
+ for q, meta_data in q_list.items():
+ act_list = []
+ if meta_data['nothing_new']:
+ continue
+ else:
+ if meta_data['new_q']:
+ act_list.append(_('new question'))
+ self.__action_count('%(num)d rev', meta_data['q_rev'],act_list)
+ self.__action_count('%(num)d ans', meta_data['new_ans'],act_list)
+ self.__action_count('%(num)d ans rev',meta_data['ans_rev'],act_list)
+ act_token = ', '.join(act_list)
+ text += '<li><a href="%s?sort=latest">%s</a> <font color="#777777">(%s)</font></li>' \
+ % (url_prefix + q.get_absolute_url(), q.title, act_token)
+ text += '</ul>'
+ if num_moot > 0:
+ text += '<p></p>'
+ text += ungettext('There is also one question which was recently '\
+ +'updated but you might not have seen its latest version.',
+ 'There are also %(num)d more questions which were recently updated '\
+ +'but you might not have seen their latest version.',num_moot) \
+ % {'num':num_moot,}
+ text += _('Perhaps you could look up previously sent forum reminders in your mailbox.')
+ text += '</p>'
+
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
+ link = url_prefix + user.get_profile_url() + '?sort=email_subscriptions'
+ text += _('go to %(link)s to change frequency of email updates or %(email)s administrator') \
+ % {'link':link, 'email':settings.ADMINS[0][1]}
+ msg = EmailMessage(subject, text, settings.DEFAULT_FROM_EMAIL, [user.email])
+ msg.content_subtype = 'html'
+ msg.send()
diff --git a/forum/management/commands/subscribe_everyone.py b/forum/management/commands/subscribe_everyone.py
new file mode 100644
index 00000000..3f8da9ec
--- /dev/null
+++ b/forum/management/commands/subscribe_everyone.py
@@ -0,0 +1,31 @@
+from django.core.management.base import NoArgsCommand
+from django.db import connection
+from django.db.models import Q, F
+from forum.models import *
+from django.core.mail import EmailMessage
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+import datetime
+import settings
+
+class Command(NoArgsCommand):
+ def handle_noargs(self,**options):
+ try:
+ self.subscribe_everyone()
+ except Exception, e:
+ print e
+ finally:
+ connection.close()
+
+ def subscribe_everyone(self):
+
+ feed_type_info = EmailFeedSetting.FEED_TYPES
+ for user in User.objects.all():
+ for feed_type in feed_type_info:
+ try:
+ feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type = feed_type[0])
+ except EmailFeedSetting.DoesNotExist:
+ feed_setting = EmailFeedSetting(subscriber=user,feed_type=feed_type[0])
+ feed_setting.frequency = 'w'
+ feed_setting.reported_at = None
+ feed_setting.save()
diff --git a/forum/managers.py b/forum/managers.py
index 31528428..1504491a 100644
--- a/forum/managers.py
+++ b/forum/managers.py
@@ -7,25 +7,6 @@ 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_accepted=False).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):
"""
@@ -70,7 +51,7 @@ class QuestionManager(models.Manager):
# 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())
+ answer_count=Answer.objects.get_answers_from_question(question).filter(deleted=False).count())
def update_view_count(self, question):
"""
@@ -93,11 +74,11 @@ class QuestionManager(models.Manager):
"""
#print datetime.datetime.now()
from forum.models import Question
- questions = list(Question.objects.filter(tagnames = question.tagnames).exclude(id=question.id).all())
+ questions = list(self.filter(tagnames = question.tagnames, deleted=False).all())
tags_list = question.tags.all()
for tag in tags_list:
- extend_questions = Question.objects.filter(tags__id = tag.id).exclude(id=question.id)[:50]
+ extend_questions = self.filter(tags__id = tag.id, deleted=False)[:50]
for item in extend_questions:
if item not in questions and len(questions) < 10:
questions.append(item)
@@ -110,10 +91,11 @@ class TagManager(models.Manager):
'UPDATE tag '
'SET used_count = ('
'SELECT COUNT(*) FROM question_tags '
- 'WHERE tag_id = tag.id'
+ 'INNER JOIN question ON question_id=question.id '
+ 'WHERE tag_id = tag.id AND question.deleted=0'
') '
'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]
diff --git a/forum/models.py b/forum/models.py
index 3d752db0..3e1e6543 100644
--- a/forum/models.py
+++ b/forum/models.py
@@ -2,7 +2,7 @@
import datetime
import hashlib
from urllib import quote_plus, urlencode
-from django.db import models
+from django.db import models, IntegrityError
from django.utils.http import urlquote as django_urlquote
from django.utils.html import strip_tags
from django.core.urlresolvers import reverse
@@ -12,33 +12,66 @@ 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 _
+from django.utils.safestring import mark_safe
+from django.contrib.sitemaps import ping_google
import django.dispatch
import settings
+import logging
+
+if settings.USE_SPHINX_SEARCH == True:
+ from djangosphinx.models import SphinxSearch
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
+def get_object_comments(self):
+ comments = self.comments.all().order_by('id')
+ return comments
+
+def post_get_last_update_info(self):
+ when = self.added_at
+ who = self.author
+ if self.last_edited_at and self.last_edited_at > when:
+ when = self.last_edited_at
+ who = self.last_edited_by
+ comments = self.comments.all()
+ if len(comments) > 0:
+ for c in comments:
+ if c.added_at > when:
+ when = c.added_at
+ who = c.user
+ return when, who
+
+class EmailFeedSetting(models.Model):
+ DELTA_TABLE = {
+ 'w':datetime.timedelta(7),
+ 'd':datetime.timedelta(1),
+ 'n':datetime.timedelta(-1),
+ }
+ FEED_TYPES = (
+ ('q_all',_('Entire forum')),
+ ('q_ask',_('Questions that I asked')),
+ ('q_ans',_('Questions that I answered')),
+ ('q_sel',_('Individually selected questions')),
+ )
+ UPDATE_FREQUENCY = (
+ ('w',_('Weekly')),
+ ('d',_('Daily')),
+ ('n',_('No email')),
+ )
+ subscriber = models.ForeignKey(User)
+ feed_type = models.CharField(max_length=16,choices=FEED_TYPES)
+ frequency = models.CharField(max_length=8,choices=UPDATE_FREQUENCY,default='n')
+ added_at = models.DateTimeField(auto_now_add=True)
+ reported_at = models.DateTimeField(null=True)
+
+ def save(self,*args,**kwargs):
+ type = self.feed_type
+ subscriber = self.subscriber
+ similar = self.__class__.objects.filter(feed_type=type,subscriber=subscriber).exclude(pk=self.id)
+ if len(similar) > 0:
+ raise IntegrityError('email feed setting already exists')
+ super(EmailFeedSetting,self).save(*args,**kwargs)
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
@@ -46,7 +79,6 @@ class Tag(models.Model):
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)
@@ -70,6 +102,14 @@ class Comment(models.Model):
class Meta:
ordering = ('-added_at',)
db_table = u'comment'
+
+ def save(self,**kwargs):
+ super(Comment,self).save(**kwargs)
+ try:
+ ping_google()
+ except Exception:
+ logging.debug('problem pinging google did you register you sitemap with google?')
+
def __unicode__(self):
return self.comment
@@ -137,6 +177,7 @@ class Question(models.Model):
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)
+ followed_by = models.ManyToManyField(User, related_name='followed_questions')
# Denormalised data
score = models.IntegerField(default=0)
vote_up_count = models.IntegerField(default=0)
@@ -156,10 +197,23 @@ class Question(models.Model):
comments = generic.GenericRelation(Comment)
votes = generic.GenericRelation(Vote)
flagged_items = generic.GenericRelation(FlaggedItem)
- email_feeds = generic.GenericRelation(EmailFeed)
+
+ if settings.USE_SPHINX_SEARCH == True:
+ search = SphinxSearch(
+ index=' '.join(settings.SPHINX_SEARCH_INDICES),
+ mode='SPH_MATCH_ALL',
+ )
+ logging.debug('have sphinx search')
objects = QuestionManager()
+ def delete(self):
+ super(Question, self).delete()
+ try:
+ ping_google()
+ except Exception:
+ logging.debug('problem pinging google did you register you sitemap with google?')
+
def save(self, **kwargs):
"""
Overridden to manually manage addition of tags when the object
@@ -170,6 +224,10 @@ class Question(models.Model):
"""
initial_addition = (self.id is None)
super(Question, self).save(**kwargs)
+ try:
+ ping_google()
+ except Exception:
+ logging.debug('problem pinging google did you register you sitemap with google?')
if initial_addition:
tags = Tag.objects.get_or_create_multiple(self.tagname_list(),
self.author)
@@ -184,7 +242,7 @@ class Question(models.Model):
return u','.join([unicode(tag) for tag in self.tagname_list()])
def get_absolute_url(self):
- return '%s%s' % (reverse('question', args=[self.id]), django_urlquote(self.title.replace(' ', '-')))
+ return '%s%s' % (reverse('question', args=[self.id]), django_urlquote(slugify(self.title)))
def has_favorite_by_user(self, user):
if not user.is_authenticated():
@@ -212,17 +270,23 @@ class Question(models.Model):
def get_latest_revision(self):
return self.revisions.all()[0]
-
- def get_user_votes_in_answers(self, user):
- content_type = ContentType.objects.get_for_model(Answer)
- query_set = Vote.objects.extra(
- tables = ['question', 'answer'],
- where = ['question.id = answer.question_id AND question.id = %s AND vote.object_id = answer.id AND vote.content_type_id = %s AND vote.user_id = %s'],
- params = [self.id, content_type.id, user.id]
- )
-
- return query_set
-
+
+ get_comments = get_object_comments
+
+ def get_last_update_info(self):
+
+ when, who = post_get_last_update_info(self)
+
+ answers = self.answers.all()
+ if len(answers) > 0:
+ for a in answers:
+ a_when, a_who = a.get_last_update_info()
+ if a_when > when:
+ when = a_when
+ who = a_who
+
+ return when, who
+
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:
@@ -251,7 +315,7 @@ class Question(models.Model):
answer_comments.append(comment)
#create the report
- if edited or comments or new_answers or modified_answers or answer_comments:
+ if edited 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})
@@ -285,6 +349,11 @@ class Question(models.Model):
class Meta:
db_table = u'question'
+class QuestionView(models.Model):
+ question = models.ForeignKey(Question, related_name='viewed')
+ who = models.ForeignKey(User, related_name='question_views')
+ when = models.DateTimeField()
+
class FavoriteQuestion(models.Model):
"""A favorite Question of a User."""
question = models.ForeignKey(Question)
@@ -295,6 +364,12 @@ class FavoriteQuestion(models.Model):
def __unicode__(self):
return '[%s] favorited at %s' %(self.user, self.added_at)
+class MarkedTag(models.Model):
+ TAG_MARK_REASONS = (('good',_('interesting')),('bad',_('ignored')))
+ tag = models.ForeignKey(Tag, related_name='user_selections')
+ user = models.ForeignKey(User, related_name='tag_selections')
+ reason = models.CharField(max_length=16, choices=TAG_MARK_REASONS)
+
class QuestionRevision(models.Model):
"""A revision of a Question."""
question = models.ForeignKey(Question, related_name='revisions')
@@ -314,7 +389,8 @@ class QuestionRevision(models.Model):
return self.question.title
def get_absolute_url(self):
- return '/%s%s/%s' % (_('questions/'),self.question.id,_('revisions'))
+ print 'in QuestionRevision.get_absolute_url()'
+ return reverse('question_revisions', args=[self.question.id])
def save(self, **kwargs):
"""Looks up the next available revision number."""
@@ -394,6 +470,16 @@ class Answer(models.Model):
objects = AnswerManager()
+ get_comments = get_object_comments
+ get_last_update_info = post_get_last_update_info
+
+ def save(self,**kwargs):
+ super(Answer,self).save(**kwargs)
+ try:
+ ping_google()
+ except Exception:
+ logging.debug('problem pinging google did you register you sitemap with google?')
+
def get_user_vote(self, user):
votes = self.votes.filter(user=user)
if votes.count() > 0:
@@ -408,7 +494,7 @@ class Answer(models.Model):
return self.question.title
def get_absolute_url(self):
- return '%s%s#%s' % (reverse('question', args=[self.question.id]), django_urlquote(self.question.title), self.id)
+ return '%s%s#%s' % (reverse('question', args=[self.question.id]), django_urlquote(slugify(self.question.title)), self.id)
class Meta:
db_table = u'answer'
@@ -426,7 +512,7 @@ class AnswerRevision(models.Model):
text = models.TextField()
def get_absolute_url(self):
- return '/%s%s/%s' % (_('answers/'),self.answer.id,_('revisions'))
+ return reverse('answer_revisions', kwargs={'id':self.answer.id})
def get_question_title(self):
return self.answer.question.title
@@ -549,7 +635,7 @@ class Book(models.Model):
questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
def get_absolute_url(self):
- return '%s' % reverse('book', args=[django_urlquote(self.short_name)])
+ return reverse('book', args=[django_urlquote(slugify(self.short_name))])
def __unicode__(self):
return self.title
@@ -588,7 +674,6 @@ class AnonymousEmail(models.Model):
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 = (
@@ -597,11 +682,30 @@ QUESTIONS_PER_PAGE_CHOICES = (
(50, u'50'),
)
+def user_is_username_taken(cls,username):
+ try:
+ cls.objects.get(username=username)
+ return True
+ except cls.MultipleObjectsReturned:
+ return True
+ except cls.DoesNotExist:
+ return False
+
+def user_get_q_sel_email_feed_frequency(self):
+ print 'looking for frequency for user %s' % self
+ try:
+ feed_setting = EmailFeedSetting.objects.get(subscriber=self,feed_type='q_sel')
+ except Exception, e:
+ print 'have error %s' % e.message
+ raise e
+ print 'have freq=%s' % feed_setting.frequency
+ return feed_setting.frequency
+
+User.add_to_class('is_approved', models.BooleanField(default=False))
User.add_to_class('email_isvalid', models.BooleanField(default=False))
User.add_to_class('email_key', models.CharField(max_length=32, 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'))
@@ -619,6 +723,16 @@ 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))
+User.add_to_class('is_username_taken',classmethod(user_is_username_taken))
+User.add_to_class('get_q_sel_email_feed_frequency',user_get_q_sel_email_feed_frequency)
+User.add_to_class('hide_ignored_questions', models.BooleanField(default=False))
+User.add_to_class('tag_filter_setting',
+ models.CharField(
+ max_length=16,
+ choices=TAG_EMAIL_FILTER_CHOICES,
+ default='ignored'
+ )
+ )
# custom signal
tags_updated = django.dispatch.Signal(providing_args=["question"])
@@ -641,7 +755,14 @@ def delete_messages(self):
def get_profile_url(self):
"""Returns the URL for this User's profile."""
return '%s%s/' % (reverse('user', args=[self.id]), slugify(self.username))
+
+def get_profile_link(self):
+ profile_link = u'<a href="%s">%s</a>' % (self.get_profile_url(),self.username)
+ logging.debug('in get profile link %s' % profile_link)
+ return mark_safe(profile_link)
+
User.add_to_class('get_profile_url', get_profile_url)
+User.add_to_class('get_profile_link', get_profile_link)
User.add_to_class('get_messages', get_messages)
User.add_to_class('delete_messages', delete_messages)
diff --git a/forum/sitemap.py b/forum/sitemap.py
new file mode 100644
index 00000000..dc97a009
--- /dev/null
+++ b/forum/sitemap.py
@@ -0,0 +1,11 @@
+from django.contrib.sitemaps import Sitemap
+from forum.models import Question
+
+class QuestionsSitemap(Sitemap):
+ changefreq = 'daily'
+ priority = 0.5
+ def items(self):
+ return Question.objects.exclude(deleted=True)
+
+ def lastmod(self, obj):
+ return obj.last_activity_at
diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py
index d8b8e61f..865cd33d 100644
--- a/forum/templatetags/extra_filters.py
+++ b/forum/templatetags/extra_filters.py
@@ -1,5 +1,10 @@
from django import template
+<<<<<<< HEAD:forum/templatetags/extra_filters.py
+=======
+from django.core import serializers
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/templatetags/extra_filters.py
from forum import auth
+import logging
register = template.Library()
@@ -9,6 +14,10 @@ def collapse(input):
return ' '.join(input.split())
@register.filter
+def can_moderate_users(user):
+ return auth.can_moderate_users(user)
+
+@register.filter
def can_vote_up(user):
return auth.can_vote_up(user)
@@ -17,8 +26,8 @@ def can_flag_offensive(user):
return auth.can_flag_offensive(user)
@register.filter
-def can_add_comments(user):
- return auth.can_add_comments(user)
+def can_add_comments(user,subject):
+ return auth.can_add_comments(user,subject)
@register.filter
def can_vote_down(user):
@@ -86,3 +95,10 @@ def cnprog_intword(number):
return number
except:
return number
+<<<<<<< HEAD:forum/templatetags/extra_filters.py
+=======
+
+@register.filter
+def json_serialize(object):
+ return serializers.serialize('json',object)
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/templatetags/extra_filters.py
diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
index 90ebb65b..b2199284 100644
--- a/forum/templatetags/extra_tags.py
+++ b/forum/templatetags/extra_tags.py
@@ -1,4 +1,5 @@
import time
+import os
import datetime
import math
import re
@@ -6,13 +7,15 @@ 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 forum.models import Question, Answer, QuestionRevision, AnswerRevision
from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+from django.conf import settings
register = template.Library()
-GRAVATAR_TEMPLATE = ('<img width="%(size)s" height="%(size)s" '
+GRAVATAR_TEMPLATE = ('<img class="gravatar" width="%(size)s" height="%(size)s" '
'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
'?s=%(size)s&amp;d=identicon&amp;r=PG" '
'alt="%(username)s\'s gravatar image" />')
@@ -115,6 +118,23 @@ def cnprog_pagesize(context):
"pagesize" : context["pagesize"],
"is_paginated": context["is_paginated"]
}
+
+@register.inclusion_tag("post_contributor_info.html")
+def post_contributor_info(post,contributor_type='original_author'):
+ """contributor_type: original_author|last_updater
+ """
+ if isinstance(post,Question):
+ post_type = 'question'
+ elif isinstance(post,Answer):
+ post_type = 'answer'
+ elif isinstance(post,AnswerRevision) or isinstance(post,QuestionRevision):
+ post_type = 'revision'
+ return {
+ 'post':post,
+ 'post_type':post_type,
+ 'wiki_on':settings.WIKI_ON,
+ 'contributor_type':contributor_type
+ }
@register.simple_tag
def get_score_badge(user):
@@ -216,21 +236,31 @@ def convert2tagname_list(question):
@register.simple_tag
def diff_date(date, limen=2):
- meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sept', 'Oct', 'Nov', 'Dic']
- current_time = datetime.datetime(*time.localtime()[0:6])
- diff = current_time - date
- diff_days = diff.days
- if diff_days > limen:
- return "%s %s - %s @ %s:%s" % (meses[date.month-1], date.day, date.year, date.hour, date.minute)
+ now = datetime.datetime.now()#datetime(*time.localtime()[0:6])#???
+ diff = now - date
+ days = diff.days
+ hours = int(diff.seconds/3600)
+ minutes = int(diff.seconds/60)
+
+ if days > 2:
+ if date.year == now.year:
+ return date.strftime(_("%b %d at %H:%M"))
+ else:
+ return date.strftime(_("%b %d '%y at %H:%M"))
+ elif days == 2:
+ return _('2 days ago')
+ elif days == 1:
+ return _('yesterday')
+ elif minutes >= 60:
+ return ungettext('%(hr)d hour ago','%(hr)d hours ago',hours) % {'hr':hours}
else:
- return timesince(date) + _(' ago')
-
+ return ungettext('%(min)d min ago','%(min)d mins ago',minutes) % {'min':minutes}
+
@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,
@@ -243,3 +273,78 @@ def get_latest_changed_timestamp():
except:
timestr = ''
return timestr
+
+@register.simple_tag
+def href(url):
+ url = '///' + settings.FORUM_SCRIPT_ALIAS + '/' + url
+ return os.path.normpath(url) + '?v=%d' % settings.RESOURCE_REVISION
+
+class ItemSeparatorNode(template.Node):
+ def __init__(self,separator):
+ sep = separator.strip()
+ if sep[0] == sep[-1] and sep[0] in ('\'','"'):
+ sep = sep[1:-1]
+ else:
+ raise template.TemplateSyntaxError('separator in joinitems tag must be quoted')
+ self.content = sep
+ def render(self,context):
+ return self.content
+
+class JoinItemListNode(template.Node):
+ def __init__(self,separator=ItemSeparatorNode("''"), items=()):
+ self.separator = separator
+ self.items = items
+ def render(self,context):
+ out = []
+ empty_re = re.compile(r'^\s*$')
+ for item in self.items:
+ bit = item.render(context)
+ if not empty_re.search(bit):
+ out.append(bit)
+ return self.separator.render(context).join(out)
+
+@register.tag(name="joinitems")
+def joinitems(parser,token):
+ try:
+ tagname,junk,sep_token = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
+ if junk == 'using':
+ sep_node = ItemSeparatorNode(sep_token)
+ else:
+ raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
+ nodelist = []
+ while True:
+ nodelist.append(parser.parse(('separator','endjoinitems')))
+ next = parser.next_token()
+ if next.contents == 'endjoinitems':
+ break
+
+ return JoinItemListNode(separator=sep_node,items=nodelist)
+
+class BlockResourceNode(template.Node):
+ def __init__(self,nodelist):
+ self.items = nodelist
+ def render(self,context):
+ out = '///' + settings.FORUM_SCRIPT_ALIAS
+ if self.items:
+ out += '/'
+ for item in self.items:
+ bit = item.render(context)
+ out += bit
+ out = os.path.normpath(out) + '?v=%d' % settings.RESOURCE_REVISION
+ return out.replace(' ','')
+
+@register.tag(name='blockresource')
+def blockresource(parser,token):
+ try:
+ tagname = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError("blockresource tag does not use arguments")
+ nodelist = []
+ while True:
+ nodelist.append(parser.parse(('endblockresource')))
+ next = parser.next_token()
+ if next.contents == 'endblockresource':
+ break
+ return BlockResourceNode(nodelist)
diff --git a/forum/templatetags/smart_if.py b/forum/templatetags/smart_if.py
new file mode 100644
index 00000000..a8fc1944
--- /dev/null
+++ b/forum/templatetags/smart_if.py
@@ -0,0 +1,401 @@
+"""
+A smarter {% if %} tag for django templates.
+
+While retaining current Django functionality, it also handles equality,
+greater than and less than operators. Some common case examples::
+
+ {% if articles|length >= 5 %}...{% endif %}
+ {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
+"""
+import unittest
+from django import template
+
+
+register = template.Library()
+
+
+#==============================================================================
+# Calculation objects
+#==============================================================================
+
+class BaseCalc(object):
+ def __init__(self, var1, var2=None, negate=False):
+ self.var1 = var1
+ self.var2 = var2
+ self.negate = negate
+
+ def resolve(self, context):
+ try:
+ var1, var2 = self.resolve_vars(context)
+ outcome = self.calculate(var1, var2)
+ except:
+ outcome = False
+ if self.negate:
+ return not outcome
+ return outcome
+
+ def resolve_vars(self, context):
+ var2 = self.var2 and self.var2.resolve(context)
+ return self.var1.resolve(context), var2
+
+ def calculate(self, var1, var2):
+ raise NotImplementedError()
+
+
+class Or(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 or var2
+
+
+class And(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 and var2
+
+
+class Equals(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 == var2
+
+
+class Greater(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 > var2
+
+
+class GreaterOrEqual(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 >= var2
+
+
+class In(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 in var2
+
+
+#==============================================================================
+# Tests
+#==============================================================================
+
+class TestVar(object):
+ """
+ A basic self-resolvable object similar to a Django template variable. Used
+ to assist with tests.
+ """
+ def __init__(self, value):
+ self.value = value
+
+ def resolve(self, context):
+ return self.value
+
+
+class SmartIfTests(unittest.TestCase):
+ def setUp(self):
+ self.true = TestVar(True)
+ self.false = TestVar(False)
+ self.high = TestVar(9000)
+ self.low = TestVar(1)
+
+ def assertCalc(self, calc, context=None):
+ """
+ Test a calculation is True, also checking the inverse "negate" case.
+ """
+ context = context or {}
+ self.assert_(calc.resolve(context))
+ calc.negate = not calc.negate
+ self.assertFalse(calc.resolve(context))
+
+ def assertCalcFalse(self, calc, context=None):
+ """
+ Test a calculation is False, also checking the inverse "negate" case.
+ """
+ context = context or {}
+ self.assertFalse(calc.resolve(context))
+ calc.negate = not calc.negate
+ self.assert_(calc.resolve(context))
+
+ def test_or(self):
+ self.assertCalc(Or(self.true))
+ self.assertCalcFalse(Or(self.false))
+ self.assertCalc(Or(self.true, self.true))
+ self.assertCalc(Or(self.true, self.false))
+ self.assertCalc(Or(self.false, self.true))
+ self.assertCalcFalse(Or(self.false, self.false))
+
+ def test_and(self):
+ self.assertCalc(And(self.true, self.true))
+ self.assertCalcFalse(And(self.true, self.false))
+ self.assertCalcFalse(And(self.false, self.true))
+ self.assertCalcFalse(And(self.false, self.false))
+
+ def test_equals(self):
+ self.assertCalc(Equals(self.low, self.low))
+ self.assertCalcFalse(Equals(self.low, self.high))
+
+ def test_greater(self):
+ self.assertCalc(Greater(self.high, self.low))
+ self.assertCalcFalse(Greater(self.low, self.low))
+ self.assertCalcFalse(Greater(self.low, self.high))
+
+ def test_greater_or_equal(self):
+ self.assertCalc(GreaterOrEqual(self.high, self.low))
+ self.assertCalc(GreaterOrEqual(self.low, self.low))
+ self.assertCalcFalse(GreaterOrEqual(self.low, self.high))
+
+ def test_in(self):
+ list_ = TestVar([1,2,3])
+ invalid_list = TestVar(None)
+ self.assertCalc(In(self.low, list_))
+ self.assertCalcFalse(In(self.low, invalid_list))
+
+ def test_parse_bits(self):
+ var = IfParser([True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([False]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([False, 'or', True]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([False, 'and', True]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser(['not', False, 'and', 'not', False]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser(['not', 'not', True]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, '=', 1]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, 'not', '=', 1]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([1, 'not', 'not', '=', 1]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, '!=', 1]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([3, '>', 2]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, '<', 2]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([2, 'not', 'in', [2, 3]]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([1, 'or', 1, '=', 2]).parse()
+ self.assert_(var.resolve({}))
+
+ def test_boolean(self):
+ var = IfParser([True, 'and', True, 'and', True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([False, 'or', False, 'or', True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([True, 'and', False, 'or', True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([False, 'or', True, 'and', True]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([True, 'and', True, 'and', False]).parse()
+ self.assertFalse(var.resolve({}))
+ var = IfParser([False, 'or', False, 'or', False]).parse()
+ self.assertFalse(var.resolve({}))
+ var = IfParser([False, 'or', True, 'and', False]).parse()
+ self.assertFalse(var.resolve({}))
+ var = IfParser([False, 'and', True, 'or', False]).parse()
+ self.assertFalse(var.resolve({}))
+
+ def test_invalid(self):
+ self.assertRaises(ValueError, IfParser(['not']).parse)
+ self.assertRaises(ValueError, IfParser(['==']).parse)
+ self.assertRaises(ValueError, IfParser([1, 'in']).parse)
+ self.assertRaises(ValueError, IfParser([1, '>', 'in']).parse)
+ self.assertRaises(ValueError, IfParser([1, '==', 'not', 'not']).parse)
+ self.assertRaises(ValueError, IfParser([1, 2]).parse)
+
+
+OPERATORS = {
+ '=': (Equals, True),
+ '==': (Equals, True),
+ '!=': (Equals, False),
+ '>': (Greater, True),
+ '>=': (GreaterOrEqual, True),
+ '<=': (Greater, False),
+ '<': (GreaterOrEqual, False),
+ 'or': (Or, True),
+ 'and': (And, True),
+ 'in': (In, True),
+}
+BOOL_OPERATORS = ('or', 'and')
+
+
+class IfParser(object):
+ error_class = ValueError
+
+ def __init__(self, tokens):
+ self.tokens = tokens
+
+ def _get_tokens(self):
+ return self._tokens
+
+ def _set_tokens(self, tokens):
+ self._tokens = tokens
+ self.len = len(tokens)
+ self.pos = 0
+
+ tokens = property(_get_tokens, _set_tokens)
+
+ def parse(self):
+ if self.at_end():
+ raise self.error_class('No variables provided.')
+ var1 = self.get_bool_var()
+ while not self.at_end():
+ op, negate = self.get_operator()
+ var2 = self.get_bool_var()
+ var1 = op(var1, var2, negate=negate)
+ return var1
+
+ def get_token(self, eof_message=None, lookahead=False):
+ negate = True
+ token = None
+ pos = self.pos
+ while token is None or token == 'not':
+ if pos >= self.len:
+ if eof_message is None:
+ raise self.error_class()
+ raise self.error_class(eof_message)
+ token = self.tokens[pos]
+ negate = not negate
+ pos += 1
+ if not lookahead:
+ self.pos = pos
+ return token, negate
+
+ def at_end(self):
+ return self.pos >= self.len
+
+ def create_var(self, value):
+ return TestVar(value)
+
+ def get_bool_var(self):
+ """
+ Returns either a variable by itself or a non-boolean operation (such as
+ ``x == 0`` or ``x < 0``).
+
+ This is needed to keep correct precedence for boolean operations (i.e.
+ ``x or x == 0`` should be ``x or (x == 0)``, not ``(x or x) == 0``).
+ """
+ var = self.get_var()
+ if not self.at_end():
+ op_token = self.get_token(lookahead=True)[0]
+ if isinstance(op_token, basestring) and (op_token not in
+ BOOL_OPERATORS):
+ op, negate = self.get_operator()
+ return op(var, self.get_var(), negate=negate)
+ return var
+
+ def get_var(self):
+ token, negate = self.get_token('Reached end of statement, still '
+ 'expecting a variable.')
+ if isinstance(token, basestring) and token in OPERATORS:
+ raise self.error_class('Expected variable, got operator (%s).' %
+ token)
+ var = self.create_var(token)
+ if negate:
+ return Or(var, negate=True)
+ return var
+
+ def get_operator(self):
+ token, negate = self.get_token('Reached end of statement, still '
+ 'expecting an operator.')
+ if not isinstance(token, basestring) or token not in OPERATORS:
+ raise self.error_class('%s is not a valid operator.' % token)
+ if self.at_end():
+ raise self.error_class('No variable provided after "%s".' % token)
+ op, true = OPERATORS[token]
+ if not true:
+ negate = not negate
+ return op, negate
+
+
+#==============================================================================
+# Actual templatetag code.
+#==============================================================================
+
+class TemplateIfParser(IfParser):
+ error_class = template.TemplateSyntaxError
+
+ def __init__(self, parser, *args, **kwargs):
+ self.template_parser = parser
+ return super(TemplateIfParser, self).__init__(*args, **kwargs)
+
+ def create_var(self, value):
+ return self.template_parser.compile_filter(value)
+
+
+class SmartIfNode(template.Node):
+ def __init__(self, var, nodelist_true, nodelist_false=None):
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+ self.var = var
+
+ def render(self, context):
+ if self.var.resolve(context):
+ return self.nodelist_true.render(context)
+ if self.nodelist_false:
+ return self.nodelist_false.render(context)
+ return ''
+
+ def __repr__(self):
+ return "<Smart If node>"
+
+ def __iter__(self):
+ for node in self.nodelist_true:
+ yield node
+ if self.nodelist_false:
+ for node in self.nodelist_false:
+ yield node
+
+ def get_nodes_by_type(self, nodetype):
+ nodes = []
+ if isinstance(self, nodetype):
+ nodes.append(self)
+ nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
+ if self.nodelist_false:
+ nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
+ return nodes
+
+
+@register.tag('if')
+def smart_if(parser, token):
+ """
+ A smarter {% if %} tag for django templates.
+
+ While retaining current Django functionality, it also handles equality,
+ greater than and less than operators. Some common case examples::
+
+ {% if articles|length >= 5 %}...{% endif %}
+ {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
+
+ Arguments and operators _must_ have a space between them, so
+ ``{% if 1>2 %}`` is not a valid smart if tag.
+
+ All supported operators are: ``or``, ``and``, ``in``, ``=`` (or ``==``),
+ ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
+ """
+ bits = token.split_contents()[1:]
+ var = TemplateIfParser(parser, bits).parse()
+ nodelist_true = parser.parse(('else', 'endif'))
+ token = parser.next_token()
+ if token.contents == 'else':
+ nodelist_false = parser.parse(('endif',))
+ parser.delete_first_token()
+ else:
+ nodelist_false = None
+ return SmartIfNode(var, nodelist_true, nodelist_false)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/forum/urls.py b/forum/urls.py
new file mode 100644
index 00000000..62e70161
--- /dev/null
+++ b/forum/urls.py
@@ -0,0 +1,91 @@
+import os.path
+from django.conf.urls.defaults import *
+from django.contrib import admin
+from forum import views as app
+from forum.feed import RssLastestQuestionsFeed
+from forum.sitemap import QuestionsSitemap
+from django.utils.translation import ugettext as _
+
+admin.autodiscover()
+feeds = {
+ 'rss': RssLastestQuestionsFeed
+}
+sitemaps = {
+ 'questions': QuestionsSitemap
+}
+
+APP_PATH = os.path.dirname(os.path.dirname(__file__))
+urlpatterns = patterns('',
+ url(r'^$', app.index, name='index'),
+ url(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}),
+ (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'^%s(?P<path>.*)$' % _('upfiles/'), 'django.views.static.serve',
+ {'document_root': os.path.join(APP_PATH, 'templates/upfiles').replace('\\','/')}
+ ),
+ (r'^%s/$' % _('signin/'), 'django_authopenid.views.signin'),
+ url(r'^%s$' % _('about/'), app.about, name='about'),
+ url(r'^%s$' % _('faq/'), app.faq, name='faq'),
+ url(r'^%s$' % _('privacy/'), app.privacy, name='privacy'),
+ url(r'^%s$' % _('logout/'), app.logout, name='logout'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('comments/')), app.answer_comments, name='answer_comments'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')), app.edit_answer, name='edit_answer'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')), app.answer_revisions, name='answer_revisions'),
+ url(r'^%s$' % _('questions/'), app.questions, name='questions'),
+ url(r'^%s%s$' % (_('questions/'), _('ask/')), app.ask, name='ask'),
+ url(r'^%s%s$' % (_('questions/'), _('unanswered/')), app.unanswered, name='unanswered'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.edit_question, name='edit_question'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.close, name='close'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.reopen, name='reopen'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.answer, name='answer'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')), app.vote, name='vote'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.question_revisions, name='question_revisions'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('comments/')), app.question_comments, name='question_comments'),
+ url(r'^%s$' % _('command/'), app.ajax_command, name='call_ajax'),
+
+ url(r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('questions/'), _('comments/'),_('delete/')), \
+ app.delete_comment, kwargs={'commented_object_type':'question'},\
+ name='delete_question_comment'),
+
+ url(r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('answers/'), _('comments/'),_('delete/')), \
+ app.delete_comment, kwargs={'commented_object_type':'answer'}, \
+ name='delete_answer_comment'), \
+ #place general question item in the end of other operations
+ url(r'^%s(?P<id>\d+)//*' % _('question/'), app.question, name='question'),
+ url(r'^%s$' % _('tags/'), app.tags, name='tags'),
+ url(r'^%s(?P<tag>[^/]+)/$' % _('tags/'), app.tag, name='tag_questions'),
+
+ url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('interesting/')), app.mark_tag, \
+ kwargs={'reason':'good','action':'add'}, \
+ name='mark_interesting_tag'),
+
+ url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('ignored/')), app.mark_tag, \
+ kwargs={'reason':'bad','action':'add'}, \
+ name='mark_ignored_tag'),
+
+ url(r'^%s(?P<tag>[^/]+)/$' % _('unmark-tag/'), app.mark_tag, \
+ kwargs={'action':'remove'}, \
+ name='mark_ignored_tag'),
+
+ url(r'^%s$' % _('users/'),app.users, name='users'),
+ url(r'^%s(?P<id>\d+)/$' % _('moderate-user/'), app.moderate_user, name='moderate_user'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), app.edit_user, name='edit_user'),
+ url(r'^%s(?P<id>\d+)//*' % _('users/'), app.user, name='user'),
+ url(r'^%s$' % _('badges/'),app.badges, name='badges'),
+ url(r'^%s(?P<id>\d+)//*' % _('badges/'), app.badge, name='badge'),
+ url(r'^%s%s$' % (_('messages/'), _('markread/')),app.read_message, name='read_message'),
+ # (r'^admin/doc/' % _('admin/doc'), include('django.contrib.admindocs.urls')),
+ (r'^%s(.*)' % _('nimda/'), admin.site.root),
+ url(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
+ (r'^%s$' % _('upload/'), app.upload),
+ url(r'^%s$' % _('books/'), app.books, name='books'),
+ url(r'^%s%s(?P<short_name>[^/]+)/$' % (_('books/'), _('ask/')), app.ask_book, name='ask_book'),
+ url(r'^%s(?P<short_name>[^/]+)/$' % _('books/'), app.book, name='book'),
+ url(r'^%s$' % _('search/'), app.search, name='search'),
+ url(r'^%s$' % _('feedback/'), app.feedback, name='feedback'),
+ (r'^%s' % _('account/'), include('django_authopenid.urls')),
+ (r'^i18n/', include('django.conf.urls.i18n')),
+)
diff --git a/forum/user.py b/forum/user.py
index 41811db9..40bf6a89 100644
--- a/forum/user.py
+++ b/forum/user.py
@@ -64,11 +64,11 @@ USER_TEMPLATE_VIEWS = (
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'
+ id = 'email_subscriptions',
+ tab_title = _('email subscriptions'),
+ tab_description = _('email subscription settings'),
+ page_title = _('profile - email subscriptions'),
+ view_name = 'user_email_subscriptions',
+ template_file = 'user_email_subscriptions.html'
)
)
diff --git a/forum/views.py b/forum/views.py
index 6c79bfbd..65b80d0e 100644
--- a/forum/views.py
+++ b/forum/views.py
@@ -2,20 +2,23 @@
import calendar
from django.conf import settings
from django.contrib.auth.decorators import login_required
-from django.contrib.contenttypes.models import ContentType
-from django.core.files.storage import default_storage
-from django.core.paginator import EmptyPage
-from django.core.paginator import InvalidPage
-from django.core.paginator import Paginator
-from django.http import Http404
-from django.http import HttpResponse
-from django.http import HttpResponseRedirect
-from django.shortcuts import get_object_or_404
-from django.shortcuts import render_to_response
-from django.template import RequestContext
-from django.utils import simplejson
+from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
+from django.core.paginator import Paginator, EmptyPage, InvalidPage
+from django.template import RequestContext, loader
from django.utils.html import *
+from django.utils import simplejson
+from django.core import serializers
+from django.core.mail import mail_admins
+from django.db import transaction
+from django.db.models import Count, Q
+from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
+from django.utils.datastructures import SortedDict
+from django.template.defaultfilters import slugify
+from django.core.exceptions import PermissionDenied
+
+from utils.html import sanitize_html
+from utils.decorators import ajax_method, ajax_login_required
from markdown2 import Markdown
import os.path
import random
@@ -29,7 +32,8 @@ from forum.diff import textDiff as htmldiff
from forum.forms import *
from forum.models import *
from forum.user import *
-from utils.html import sanitize_html
+from forum import auth
+from django_authopenid.util import get_next_url
# used in index page
INDEX_PAGE_SIZE = 20
@@ -65,42 +69,85 @@ def _get_tags_cache_json():
tags = simplejson.dumps(tags_list)
return tags
+def _get_and_remember_questions_sort_method(request, view_dic, default):
+ if default not in view_dic:
+ raise Exception('default value must be in view_dic')
+
+ q_sort_method = request.REQUEST.get('sort', None)
+ if q_sort_method == None:
+ q_sort_method = request.session.get('questions_sort_method', default)
+
+ if q_sort_method not in view_dic:
+ q_sort_method = default
+ request.session['questions_sort_method'] = q_sort_method
+ return q_sort_method, view_dic[q_sort_method]
+
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 = "-added_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)
+ "latest":"-last_activity_at",
+ "hottest":"-answer_count",
+ "mostvoted":"-score",
+ }
+ view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest')
+
+ page_size = request.session.get('pagesize', QUESTIONS_PAGE_SIZE)
+ questions = Question.objects.exclude(deleted=True).order_by(orderby)[: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()
+ (interesting_tag_names, ignored_tag_names) = (None, None)
+ if request.user.is_authenticated():
+ pt = MarkedTag.objects.filter(user=request.user)
+ interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
+ ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
+
+ tags_autocomplete = _get_tags_cache_json()
+
return render_to_response('index.html', {
- "questions": questions,
- "tab_id": view_id,
- "tags": tags,
- "awards": awards[:INDEX_AWARD_SIZE],
- }, context_instance=RequestContext(request))
+ 'interesting_tag_names': interesting_tag_names,
+ 'tags_autocomplete': tags_autocomplete,
+ 'ignored_tag_names': ignored_tag_names,
+ "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))
+ data = {
+ 'gravatar_faq_url': reverse('faq') + '#gravatar',
+ 'send_email_key_url': reverse('send_email_key'),
+ 'ask_question_url': reverse('ask'),
+ }
+ return render_to_response('faq.html', data, context_instance=RequestContext(request))
+
+def feedback(request):
+ data = {}
+ form = None
+ if request.method == "POST":
+ form = FeedbackForm(request.POST)
+ if form.is_valid():
+ if not request.user.is_authenticated:
+ data['email'] = form.cleaned_data.get('email',None)
+ data['message'] = form.cleaned_data['message']
+ data['name'] = form.cleaned_data.get('name',None)
+ message = render_to_response('feedback_email.txt',data,context_instance=RequestContext(request))
+ mail_admins(_('Q&A forum feedback'), message)
+ msg = _('Thanks for the feedback!')
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect(get_next_url(request))
+ else:
+ form = FeedbackForm(initial={'next':get_next_url(request)})
+
+ data['form'] = form
+ return render_to_response('feedback.html', data, context_instance=RequestContext(request))
+feedback.CANCEL_MESSAGE=_('We look forward to hearing your feedback! Please, give it next time :)')
def privacy(request):
return render_to_response('privacy.html', context_instance=RequestContext(request))
@@ -113,7 +160,7 @@ def questions(request, tagname=None, unanswered=False):
List of Questions, Tagged questions, and Unanswered questions.
"""
# template file
- # "questions.html" or "unanswered.html"
+ # "questions.html" or maybe index.html in the future
template_file = "questions.html"
# get pagesize from session, if failed then get default value
pagesize = request.session.get("pagesize", 10)
@@ -122,27 +169,65 @@ def questions(request, tagname=None, unanswered=False):
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"
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ view_id, orderby = _get_and_remember_questions_sort_method(request,view_dic,'latest')
# check if request is from tagged questions
+ qs = Question.objects.exclude(deleted=True)
+
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)
+ qs = qs.filter(tags__name = unquote(tagname))
- # RISK - inner join queries
- objects = objects.select_related(depth=1);
- objects_list = Paginator(objects, pagesize)
+ if unanswered:
+ qs = qs.exclude(answer_accepted=True)
+
+ author_name = None
+ #user contributed questions & answers
+ if 'user' in request.GET:
+ try:
+ author_name = request.GET['user']
+ u = User.objects.get(username=author_name)
+ qs = qs.filter(Q(author=u) | Q(answers__author=u))
+ except User.DoesNotExist:
+ author_name = None
+
+ if request.user.is_authenticated():
+ uid_str = str(request.user.id)
+ qs = qs.extra(
+ select = SortedDict([
+ (
+ 'interesting_score',
+ 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
+ + 'WHERE forum_markedtag.user_id = %s '
+ + 'AND forum_markedtag.tag_id = question_tags.tag_id '
+ + 'AND forum_markedtag.reason = "good" '
+ + 'AND question_tags.question_id = question.id'
+ ),
+ ]),
+ select_params = (uid_str,),
+ )
+ if request.user.hide_ignored_questions:
+ ignored_tags = Tag.objects.filter(user_selections__reason='bad',
+ user_selections__user = request.user)
+ qs = qs.exclude(tags__in=ignored_tags)
+ else:
+ qs = qs.extra(
+ select = SortedDict([
+ (
+ 'ignored_score',
+ 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
+ + 'WHERE forum_markedtag.user_id = %s '
+ + 'AND forum_markedtag.tag_id = question_tags.tag_id '
+ + 'AND forum_markedtag.reason = "bad" '
+ + 'AND question_tags.question_id = question.id'
+ )
+ ]),
+ select_params = (uid_str, )
+ )
+
+ qs = qs.select_related(depth=1).order_by(orderby)
+
+ objects_list = Paginator(qs, pagesize)
questions = objects_list.page(page)
# Get related tags from this page objects
@@ -150,28 +235,41 @@ def questions(request, tagname=None, unanswered=False):
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))
+ tags_autocomplete = _get_tags_cache_json()
+
+ # get the list of interesting and ignored tags
+ (interesting_tag_names, ignored_tag_names) = (None, None)
+ if request.user.is_authenticated():
+ pt = MarkedTag.objects.filter(user=request.user)
+ interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
+ ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
-def create_new_answer(question=None, author=None, \
- added_at=None, wiki=False, \
- text='', email_notify=False):
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "author_name" : author_name,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "tags_autocomplete" : tags_autocomplete,
+ "searchtag" : tagname,
+ "is_unanswered" : unanswered,
+ "interesting_tag_names": interesting_tag_names,
+ 'ignored_tag_names': ignored_tag_names,
+ "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))
@@ -208,16 +306,12 @@ def create_new_answer(question=None, author=None, \
#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()
+ if author not in question.followed_by.all():
+ question.followed_by.add(author)
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()
+ question.followed_by.remove(author)
except:
pass
@@ -267,7 +361,7 @@ def ask(request):
if form.is_valid():
added_at = datetime.datetime.now()
- title = strip_tags(form.cleaned_data['title']).strip()
+ title = strip_tags(form.cleaned_data['title'].strip())
wiki = form.cleaned_data['wiki']
tagnames = form.cleaned_data['tags'].strip()
text = form.cleaned_data['text']
@@ -301,28 +395,42 @@ def ask(request):
ip_addr=request.META['REMOTE_ADDR'],
)
question.save()
- return HttpResponseRedirect('%s%s%s' % (_('/account/'), _('signin/'), ('newquestion/')))
+ return HttpResponseRedirect(reverse('user_signin_new_question'))
else:
form = AskForm()
tags = _get_tags_cache_json()
return render_to_response('ask.html', {
- 'form': form,
- 'tags': tags,
- }, context_instance=RequestContext(request))
+ 'form' : form,
+ 'tags' : tags,
+ 'email_validation_faq_url':reverse('faq') + '#validate',
+ }, 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"}
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
try:
orderby = view_dic[view_id]
except KeyError:
- view_id = "votes"
- orderby = "-score"
+ qsm = request.session.get('questions_sort_method',None)
+ if qsm in ('mostvoted','latest'):
+ logging.debug('loaded from session ' + qsm)
+ if qsm == 'mostvoted':
+ view_id = 'votes'
+ orderby = '-score'
+ else:
+ view_id = 'latest'
+ orderby = '-added_at'
+ else:
+ view_id = "votes"
+ orderby = "-score"
+
+ logging.debug('view_id=' + str(view_id))
question = get_object_or_404(Question, id=id)
if question.deleted and not can_view_deleted_post(request.user, question):
@@ -345,8 +453,11 @@ def question(request, id):
vote_value = -1
if vote.is_upvote():
vote_value = 1
- user_answer_votes[vote.object_id] = vote_value
-
+ 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:
@@ -357,8 +468,38 @@ def question(request, id):
objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE)
page_objects = objects_list.page(page)
- # update view count
- Question.objects.update_view_count(question)
+
+ #todo: merge view counts per user and per session
+ #1) view count per session
+ update_view_count = False
+ if 'question_view_times' not in request.session:
+ request.session['question_view_times'] = {}
+
+ last_seen = request.session['question_view_times'].get(question.id,None)
+ updated_when, updated_who = question.get_last_update_info()
+
+ if updated_who != request.user:
+ if last_seen:
+ if last_seen < updated_when:
+ update_view_count = True
+ else:
+ update_view_count = True
+
+ request.session['question_view_times'][question.id] = datetime.datetime.now()
+
+ if update_view_count:
+ question.view_count += 1
+ question.save()
+
+ #2) question view count per user
+ if request.user.is_authenticated():
+ try:
+ question_view = QuestionView.objects.get(who=request.user, question=question)
+ except QuestionView.DoesNotExist:
+ question_view = QuestionView(who=request.user, question=question)
+ question_view.when = datetime.datetime.now()
+ question_view.save()
+
return render_to_response('question.html', {
"question": question,
"question_vote": question_vote,
@@ -472,7 +613,6 @@ def _retag_question(request, question):
'tags': _get_tags_cache_json(),
}, context_instance=RequestContext(request))
-
def _edit_question(request, question):
latest_revision = question.get_latest_revision()
revision_form = None
@@ -617,6 +757,7 @@ QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n'
def question_revisions(request, id):
post = get_object_or_404(Question, id=id)
revisions = list(post.revisions.all())
+ revisions.reverse()
for i, revision in enumerate(revisions):
revision.html = QUESTION_REVISION_TEMPLATE % {
'title': revision.title,
@@ -625,16 +766,15 @@ def question_revisions(request, id):
for tag in revision.tagnames.split(' ')]),
}
if i > 0:
- revisions[i - 1].diff = htmldiff(revision.html,
- revisions[i - 1].html)
+ revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
else:
- revisions[i - 1].diff = QUESTION_REVISION_TEMPLATE % {
+ revisions[i].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
+ revisions[i].summary = _('initial version')
return render_to_response('revisions_question.html', {
'post': post,
'revisions': revisions,
@@ -644,16 +784,16 @@ 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())
+ revisions.reverse()
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)
+ revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
else:
- revisions[i - 1].diff = revisions[i-1].text
- revisions[i - 1].summary = None
+ revisions[i].diff = revisions[i].text
+ revisions[i].summary = _('initial version')
return render_to_response('revisions_answer.html', {
'post': post,
'revisions': revisions,
@@ -691,8 +831,7 @@ def answer(request, id):
ip_addr=request.META['REMOTE_ADDR'],
)
anon.save()
- return HttpResponseRedirect('/%s%s%s%s' % ( _('account/'),
- _('signin/'),'?next=', question.get_absolute_url()))
+ return HttpResponseRedirect(reverse('user_signin_new_answer'))
return HttpResponseRedirect(question.get_absolute_url())
@@ -707,7 +846,7 @@ def tags(request):
if request.method == "GET":
stag = request.GET.get("q", "").strip()
- if len(stag) > 0:
+ if stag != '':
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 == "used":
@@ -721,22 +860,21 @@ def tags(request):
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))
+ "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' : reverse('tags') + '?sort=%s&' % sortby
+ }
+ }, context_instance=RequestContext(request))
def tag(request, tag):
return questions(request, tagname=tag)
@@ -925,7 +1063,8 @@ def vote(request, id):
if not can_delete_post(request.user, post):
response_data['allowed'] = -2
- elif post.deleted:
+ elif post.deleted == True:
+ logging.debug('debug restoring post in view')
onDeleteCanceled(post, request.user)
response_data['status'] = 1
else:
@@ -934,13 +1073,19 @@ def vote(request, id):
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 user not in question.followed_by.all():
+ question.followed_by.add(user)
if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False:
- response_data['message'] = _('subscription saved, %(email)s needs validation') % {'email':user.email}
+ response_data['message'] = \
+ _('subscription saved, %(email)s needs validation, see %(details_url)s') \
+ % {'email':user.email,'details_url':reverse('faq') + '#validate'}
+ feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type='q_sel')
+ if feed_setting.frequency == 'n':
+ feed_setting.frequency = 'd'
+ feed_setting.save()
+ if 'message' in response_data:
+ response_data['message'] += '<br/>'
+ response_data['message'] = _('email update frequency has been set to daily')
#response_data['status'] = 1
#responst_data['allowed'] = 1
else:
@@ -950,12 +1095,8 @@ def vote(request, id):
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
-
+ if user in question.followed_by.all():
+ question.followed_by.remove(user)
else:
response_data['success'] = 0
response_data['message'] = u'Request mode is not supported. Please try again.'
@@ -967,6 +1108,42 @@ def vote(request, id):
data = simplejson.dumps(response_data)
return HttpResponse(data, mimetype="application/json")
+@ajax_login_required
+def mark_tag(request, tag=None, **kwargs):
+ action = kwargs['action']
+ ts = MarkedTag.objects.filter(user=request.user, tag__name=tag)
+ if action == 'remove':
+ logging.debug('deleting tag %s' % tag)
+ ts.delete()
+ else:
+ reason = kwargs['reason']
+ if len(ts) == 0:
+ try:
+ t = Tag.objects.get(name=tag)
+ mt = MarkedTag(user=request.user, reason=reason, tag=t)
+ mt.save()
+ except:
+ pass
+ else:
+ ts.update(reason=reason)
+ return HttpResponse(simplejson.dumps(''), mimetype="application/json")
+
+@ajax_login_required
+def ajax_toggle_ignored_questions(request):
+ if request.user.hide_ignored_questions:
+ new_hide_setting = False
+ else:
+ new_hide_setting = True
+ request.user.hide_ignored_questions = new_hide_setting
+ request.user.save()
+
+@ajax_method
+def ajax_command(request):
+ if 'command' not in request.POST:
+ return HttpResponseForbidden(mimetype="application/json")
+ if request.POST['command'] == 'toggle-ignored-questions':
+ return ajax_toggle_ignored_questions(request)
+
def users(request):
is_paginated = True
sortby = request.GET.get('sort', 'reputation')
@@ -986,11 +1163,11 @@ def users(request):
# default
else:
objects_list = Paginator(User.objects.all().order_by('-reputation'), USERS_PAGE_SIZE)
- base_url = '/%s?sort=%s&' % (_('users/'), sortby)
+ base_url = reverse('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 = '/%s?name=%s&sort=%s&' % (_('users/'), suser, sortby)
+ base_url = reverse('users') + '?name=%s&sort=%s&' % (suser, sortby)
try:
users = objects_list.page(page)
@@ -998,22 +1175,22 @@ def users(request):
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))
+ "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')
@@ -1023,6 +1200,26 @@ def user(request, id):
return func(request, id, user_view)
@login_required
+def moderate_user(request, id):
+ """ajax handler of user moderation
+ """
+ if not auth.can_moderate_users(request.user) or request.method != 'POST':
+ raise Http404
+ if not request.is_ajax():
+ return HttpResponseForbidden(mimetype="application/json")
+
+ user = get_object_or_404(User, id=id)
+ form = ModerateUserForm(request.POST, instance=user)
+
+ if form.is_valid():
+ form.save()
+ logging.debug('data saved')
+ response = HttpResponse(simplejson.dumps(''), mimetype="application/json")
+ else:
+ response = HttpResponseForbidden(mimetype="application/json")
+ return response
+
+@login_required
def edit_user(request, id):
user = get_object_or_404(User, id=id)
if request.user != user:
@@ -1035,6 +1232,7 @@ def edit_user(request, id):
from django_authopenid.views import set_new_email
set_new_email(user, new_email)
+ user.username = sanitize_html(form.cleaned_data['username'])
user.real_name = sanitize_html(form.cleaned_data['realname'])
user.website = sanitize_html(form.cleaned_data['website'])
user.location = sanitize_html(form.cleaned_data['city'])
@@ -1052,8 +1250,9 @@ def edit_user(request, id):
else:
form = EditUserForm(user)
return render_to_response('user_edit.html', {
- 'form': form,
- }, context_instance=RequestContext(request))
+ 'form' : form,
+ 'gravatar_faq_url' : reverse('faq') + '#gravatar',
+ }, context_instance=RequestContext(request))
def user_stats(request, user_id, user_view):
user = get_object_or_404(User, id=user_id)
@@ -1097,74 +1296,99 @@ def user_stats(request, user_id, user_view):
'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]
+ 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 question.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]
+ question_id_set = set(map(lambda v: v['id'], list(questions))) \
+ | set(map(lambda v: v['id'], list(answered_questions)))
+
+ user_tags = Tag.objects.filter(questions__id__in = question_id_set)
try:
from django.db.models import Count
awards = Award.objects.extra(
- select={'id': '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', 'name', 'description', 'type')
+ select={'id': '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', 'name', 'description', 'type')
total_awards = awards.count()
- awards = awards.annotate(count=Count('badge__id'))
+ awards = awards.annotate(count = Count('badge__id'))
+ user_tags = user_tags.annotate(user_tag_usage_count=Count('name'))
+
except ImportError:
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')
+ 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']
+ user_tags = user_tags.extra(
+ select={'user_tag_usage_count': 'COUNT(1)',},
+ order_by=['-user_tag_usage_count'],
+ )
+ user_tags.query.group_by = ['name']
- 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))
+ if auth.can_moderate_users(request.user):
+ moderate_user_form = ModerateUserForm(instance=user)
+ else:
+ moderate_user_form = None
+
+ return render_to_response(user_view.template_file,{
+ 'moderate_user_form': moderate_user_form,
+ "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,
+ "user_tags" : user_tags[:50],
+ "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)
@@ -1180,8 +1404,10 @@ def user_recent(request, user_id, user_view):
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)
+ slug_title = slugify(title)
+ self.title_link = reverse('question', kwargs={'id':question_id}) + u'%s' % slug_title
+ if int(answer_id) > 0:
+ self.title_link += '#%s' % answer_id
class AwardEvent:
def __init__(self, time, type, id):
@@ -1193,23 +1419,23 @@ def user_recent(request, user_id, user_view):
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'
- )
+ 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 question.deleted=0 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]
@@ -1217,25 +1443,26 @@ def user_recent(request, user_id, user_view):
# 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'
- )
+ 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 answer.deleted=0 AND activity.user_id=%s AND '+
+ 'activity.activity_type=%s AND question.deleted=0'],
+ 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]
@@ -1243,25 +1470,26 @@ def user_recent(request, user_id, user_view):
# 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'
- )
+ 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 AND ' +
+ 'question.deleted=0'],
+ 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', \
@@ -1270,28 +1498,29 @@ def user_recent(request, user_id, user_view):
# 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'
- )
+ 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 AND '+
+ 'answer.deleted=0 AND question.deleted=0'],
+ 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'], \
@@ -1300,26 +1529,27 @@ def user_recent(request, user_id, user_view):
# 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'
- )
+ 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', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = question_revision.id AND '+
+ 'question_revision.id=question.id AND question.deleted=0 AND '+
+ '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', \
@@ -1328,30 +1558,31 @@ def user_recent(request, user_id, user_view):
# 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'
- )
+ 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 '+
+ 'question.deleted=0 AND answer.deleted=0 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'], \
@@ -1360,24 +1591,25 @@ def user_recent(request, user_id, user_view):
# 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',
- )
+ 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.deleted=0 AND question.deleted=0 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]
@@ -1405,13 +1637,13 @@ def user_recent(request, user_id, user_view):
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))
+ 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):
"""
@@ -1421,9 +1653,9 @@ def user_responses(request, user_id, user_view):
def __init__(self, type, title, question_id, answer_id, time, username, user_id, content):
self.type = type
self.title = title
- self.titlelink = u'/%s%s/%s#%s' % (_('questions/'), question_id, slugify(title), answer_id)
+ self.titlelink = reverse('question', args=[question_id]) + u'%s#%s' % (slugify(title), answer_id)
self.time = time
- self.userlink = u'/%s%s/%s/' % (_('users/'), user_id, username)
+ self.userlink = reverse('users') + u'%s/%s/' % (user_id, username)
self.username = username
self.content = u'%s ...' % strip_tags(content)[:300]
@@ -1433,30 +1665,31 @@ def user_responses(request, user_id, user_view):
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'
- )
+ 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]
@@ -1465,27 +1698,27 @@ def user_responses(request, user_id, user_view):
# 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'
- )
+ 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'],
@@ -1719,88 +1952,117 @@ def user_favorites(request, user_id, user_view):
"view_user": user
}, context_instance=RequestContext(request))
-
-def user_preferences(request, user_id, user_view):
+def user_email_subscriptions(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))
+ if request.method == 'POST':
+ email_feeds_form = EditUserEmailFeedsForm(request.POST)
+ tag_filter_form = TagFilterSelectionForm(request.POST, instance=user)
+ if email_feeds_form.is_valid() and tag_filter_form.is_valid():
+
+ action_status = None
+ tag_filter_saved = tag_filter_form.save()
+ if tag_filter_saved:
+ action_status = _('changes saved')
+ if 'save' in request.POST:
+ feeds_saved = email_feeds_form.save(user)
+ if feeds_saved:
+ action_status = _('changes saved')
+ elif 'stop_email' in request.POST:
+ email_stopped = email_feeds_form.reset().save(user)
+ initial_values = EditUserEmailFeedsForm.NO_EMAIL_INITIAL
+ email_feeds_form = EditUserEmailFeedsForm(initial=initial_values)
+ if email_stopped:
+ action_status = _('email updates canceled')
+ else:
+ email_feeds_form = EditUserEmailFeedsForm()
+ email_feeds_form.set_initial_values(user)
+ tag_filter_form = TagFilterSelectionForm(instance=user)
+ action_status = None
+ 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,
+ 'email_feeds_form':email_feeds_form,
+ 'tag_filter_selection_form':tag_filter_form,
+ 'action_status':action_status,
+ }, 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)
+ return __comments(request, question, 'question')
def answer_comments(request, id):
answer = get_object_or_404(Answer, id=id)
user = request.user
- return __comments(request, answer, 'answer', user)
+ return __comments(request, answer, 'answer')
-def __comments(request, obj, type, user):
+def __comments(request, obj, type):
# only support get comments by ajax now
+ user = request.user
if request.is_ajax():
if request.method == "GET":
- return __generate_comments_json(obj, type, user)
+ response = __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)
+ if auth.can_add_comments(user,obj):
+ 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()
+ response = __generate_comments_json(obj, type, user)
+ else:
+ response = HttpResponseForbidden(mimetype="application/json")
+ return response
def __generate_comments_json(obj, type, user):
- comments = obj.comments.all().order_by('-id')
+ 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 = []
+ from forum.templatetags.extra_tags import diff_date
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
- })
+ #todo translate this url
+ delete_url = reverse(index) + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
+ json_comments.append({"id" : comment.id,
+ "object_id" : obj.id,
+ "comment_age" : diff_date(comment.added_at),
+ "text" : comment.comment,
+ "user_display_name" : comment_user.username,
+ "user_url" : comment_user.get_profile_url(),
+ "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_comment(request, object_id='', comment_id='', commented_object_type=None):
+ response = None
+ commented_object = None
+ if commented_object_type == 'question':
+ commented_object = Question
+ elif commented_object_type == 'answer':
+ commented_object = Answer
-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)
+ if auth.can_delete_comment(request.user, comment):
+ obj = get_object_or_404(commented_object, id=object_id)
+ obj.comments.remove(comment)
+ obj.comment_count = obj.comment_count - 1
+ obj.save()
+ user = request.user
+ return __generate_comments_json(obj, commented_object_type, user)
+ raise PermissionDenied()
def logout(request):
- url = request.GET.get('next')
return render_to_response('logout.html', {
- 'next': url,
- }, context_instance=RequestContext(request))
+ 'next' : get_next_url(request),
+ }, context_instance=RequestContext(request))
def badges(request):
badges = Badge.objects.all().order_by('type')
@@ -1810,9 +2072,10 @@ def badges(request):
my_badges.query.group_by = ['badge_id']
return render_to_response('badges.html', {
- 'badges': badges,
- 'mybadges': my_badges,
- }, context_instance=RequestContext(request))
+ 'badges' : badges,
+ 'mybadges' : my_badges,
+ 'feedback_faq_url' : reverse('feedback'),
+ }, context_instance=RequestContext(request))
def badge(request, id):
badge = get_object_or_404(Badge, id=id)
@@ -1863,8 +2126,8 @@ def upload(request):
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
+ # generate 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
@@ -1887,7 +2150,7 @@ def upload(request):
return HttpResponse(result, mimetype="application/xml")
def books(request):
- return HttpResponseRedirect("/books/mysql-zhaoyang")
+ return HttpResponseRedirect(reverse('books') + '/mysql-zhaoyang')
def book(request, short_name, unanswered=False):
"""
@@ -2010,9 +2273,10 @@ def ask_book(request, short_name):
tags = _get_tags_cache_json()
return render_to_response('ask.html', {
- 'form': form,
- 'tags': tags,
- }, context_instance=RequestContext(request))
+ 'form' : form,
+ 'tags' : tags,
+ 'email_validation_faq_url': reverse('faq') + '#validate',
+ }, context_instance=RequestContext(request))
def search(request):
"""
@@ -2027,11 +2291,11 @@ def search(request):
except ValueError:
page = 1
if keywords is None:
- return HttpResponseRedirect('/')
+ return HttpResponseRedirect(reverse(index))
if search_type == 'tag':
- return HttpResponseRedirect('/%s?q=%s&page=%s' % (_('tags/'), keywords.strip(), page))
+ return HttpResponseRedirect(reverse('tags') + '?q=%s&page=%s' % (keywords.strip(), page))
elif search_type == "user":
- return HttpResponseRedirect('/%s?q=%s&page=%s' % (_('users/'), keywords.strip(), page))
+ return HttpResponseRedirect(reverse('users') + '?q=%s&page=%s' % (keywords.strip(), page))
elif search_type == "question":
template_file = "questions.html"
@@ -2070,10 +2334,16 @@ def search(request):
view_id = "latest"
orderby = "-added_at"
- objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
+ if settings.USE_SPHINX_SEARCH == True:
+ #search index is now free of delete questions and answers
+ #so there is not "antideleted" filtering here
+ objects = Question.search.query(keywords)
+ #no related selection either because we're relying on full text search here
+ else:
+ objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
+ # RISK - inner join queries
+ objects = objects.select_related();
- # RISK - inner join queries
- objects = objects.select_related();
objects_list = Paginator(objects, pagesize)
questions = objects_list.page(page)
diff --git a/junk.py b/junk.py
new file mode 100644
index 00000000..c6c03d27
--- /dev/null
+++ b/junk.py
@@ -0,0 +1,3 @@
+import os
+
+print os.path.normpath('/haha//haha')
diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo
index cd8de560..502c1075 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 b5e5c549..3f554733 100644
--- a/locale/en/LC_MESSAGES/django.po
+++ b/locale/en/LC_MESSAGES/django.po
@@ -8,347 +8,331 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-08-19 00:41+0000\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"POT-Creation-Date: 2009-12-09 08:54-0800\n"
+"PO-Revision-Date: 2009-12-15 16:53-0600\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: settings.py:12 urls.py:25 forum/views.py:305 forum/views.py:699
-msgid "account/"
-msgstr ""
-
-#: settings.py:12 urls.py:26 django_authopenid/urls.py:9
-#: django_authopenid/urls.py:10 django_authopenid/urls.py:11
-#: django_authopenid/urls.py:13 forum/views.py:305 forum/views.py:700
-#: templates/authopenid/confirm_email.txt:10
-msgid "signin/"
-msgstr ""
-
-#: urls.py:22
-msgid "upfiles/"
-msgstr ""
-
-#: urls.py:27 urls.py:28 urls.py:29 django_authopenid/urls.py:26
-#: django_authopenid/urls.py:27
-msgid "email/"
-msgstr ""
-
-#: urls.py:27
-msgid "change/"
-msgstr ""
-
-#: urls.py:28
-msgid "sendkey/"
-msgstr ""
-
-#: urls.py:29
-msgid "verify/"
-msgstr ""
+#: django_authopenid/forms.py:70
+msgid "choose a username"
+msgstr "Choose screen name"
-#: urls.py:30
-msgid "about/"
+#: django_authopenid/forms.py:76
+msgid "user name is required"
msgstr ""
-#: urls.py:31
-msgid "faq/"
+#: django_authopenid/forms.py:77
+msgid "sorry, this name is taken, please choose another"
msgstr ""
-#: urls.py:32
-msgid "privacy/"
+#: django_authopenid/forms.py:78
+msgid "sorry, this name is not allowed, please choose another"
msgstr ""
-#: urls.py:33
-msgid "logout/"
+#: django_authopenid/forms.py:79
+msgid "sorry, there is no user with this name"
msgstr ""
-#: urls.py:34 urls.py:35 urls.py:36 urls.py:48 forum/models.py:418
-msgid "answers/"
+#: django_authopenid/forms.py:80
+msgid "sorry, we have a serious error - user name is taken by several users"
msgstr ""
-#: urls.py:34 urls.py:46
-msgid "comments/"
+#: django_authopenid/forms.py:81
+msgid "user name can only consist of letters, empty space and underscore"
msgstr ""
-#: urls.py:35 urls.py:40 urls.py:54 templates/user_info.html:34
-msgid "edit/"
-msgstr ""
+#: django_authopenid/forms.py:116
+msgid "your email address"
+msgstr "Your email <i>(never shared)</i>"
-#: urls.py:36 urls.py:45
-msgid "revisions/"
+#: django_authopenid/forms.py:117
+msgid "email address is required"
msgstr ""
-#: urls.py:37 urls.py:38 urls.py:39 urls.py:40 urls.py:41 urls.py:42
-#: urls.py:43 urls.py:44 urls.py:45 urls.py:46 urls.py:47 forum/feed.py:19
-#: forum/models.py:306 forum/views.py:1188 forum/views.py:1190
-#: forum/views.py:1430
-msgid "questions/"
+#: django_authopenid/forms.py:118
+msgid "please enter a valid email address"
msgstr ""
-#: urls.py:38 urls.py:64
-msgid "ask/"
+#: django_authopenid/forms.py:119
+msgid "this email is already used by someone else, please choose another"
msgstr ""
-#: urls.py:39
-msgid "unanswered/"
+#: django_authopenid/forms.py:163 django_authopenid/views.py:118
+msgid "i-names are not supported"
msgstr ""
-#: urls.py:41
-msgid "close/"
+#: django_authopenid/forms.py:219
+msgid "Account with this name already exists on the forum"
msgstr ""
-#: urls.py:42
-msgid "reopen/"
+#: django_authopenid/forms.py:220
+msgid "can't have two logins to the same account yet, sorry."
msgstr ""
-#: urls.py:43
-msgid "answer/"
+#: django_authopenid/forms.py:242
+msgid "Please enter valid username and password (both are case-sensitive)."
msgstr ""
-#: urls.py:44
-msgid "vote/"
+#: django_authopenid/forms.py:245 django_authopenid/forms.py:295
+msgid "This account is inactive."
msgstr ""
-#: urls.py:47 urls.py:48 django_authopenid/urls.py:29
-msgid "delete/"
+#: django_authopenid/forms.py:247
+msgid "Login failed."
msgstr ""
-#: urls.py:50
-msgid "question/"
+#: django_authopenid/forms.py:249
+msgid "Please enter username and password"
msgstr ""
-#: urls.py:51 urls.py:52 forum/views.py:741 forum/views.py:2027
-msgid "tags/"
+#: django_authopenid/forms.py:251
+msgid "Please enter your password"
msgstr ""
-#: urls.py:53 urls.py:54 urls.py:55 forum/views.py:994 forum/views.py:998
-#: forum/views.py:1432 forum/views.py:1765 forum/views.py:2029
-msgid "users/"
+#: django_authopenid/forms.py:253
+msgid "Please enter user name"
msgstr ""
-#: urls.py:56 urls.py:57
-msgid "badges/"
+#: django_authopenid/forms.py:291
+msgid ""
+"Please enter a valid username and password. Note that "
+"both fields are case-sensitive."
msgstr ""
-#: urls.py:58
-msgid "messages/"
-msgstr ""
+#: django_authopenid/forms.py:313
+msgid "choose password"
+msgstr "Password"
-#: urls.py:58
-msgid "markread/"
+#: django_authopenid/forms.py:314
+msgid "password is required"
msgstr ""
-#: urls.py:60
-msgid "nimda/"
-msgstr ""
+#: django_authopenid/forms.py:317
+msgid "retype password"
+msgstr "Password <i>(please retype)</i>"
-#: urls.py:62
-msgid "upload/"
+#: django_authopenid/forms.py:318
+msgid "please, retype your password"
msgstr ""
-#: urls.py:63 urls.py:64 urls.py:65
-msgid "books/"
+#: django_authopenid/forms.py:319
+msgid "sorry, entered passwords did not match, please try again"
msgstr ""
-#: urls.py:66
-msgid "search/"
+#: django_authopenid/forms.py:344
+msgid "Current password"
msgstr ""
-#: django_authopenid/forms.py:67 django_authopenid/views.py:102
-msgid "i-names are not supported"
+#: django_authopenid/forms.py:346
+msgid "New password"
msgstr ""
-#: django_authopenid/forms.py:102
-msgid ""
-"Usernames can only contain letters, numbers and "
-"underscores"
+#: django_authopenid/forms.py:348
+msgid "Retype new password"
msgstr ""
-#: django_authopenid/forms.py:109
+#: django_authopenid/forms.py:359
msgid ""
-"This username does not exist in our database. Please "
-"choose another."
+"Old password is incorrect. Please enter the correct "
+"password."
msgstr ""
-#: 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."
+#: django_authopenid/forms.py:371
+msgid "new passwords do not match"
msgstr ""
-#: django_authopenid/forms.py:130 django_authopenid/forms.py:237
-msgid "This account is inactive."
+#: django_authopenid/forms.py:435
+msgid "Your user name (<i>required</i>)"
msgstr ""
-#: django_authopenid/forms.py:158 django_authopenid/forms.py:210
-msgid "invalid user name"
-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"
-msgstr ""
+#: django_authopenid/forms.py:450
+msgid "Incorrect username."
+msgstr "sorry, there is no such user name"
-#: django_authopenid/forms.py:162
-msgid "username too short"
+#: django_authopenid/urls.py:9 django_authopenid/urls.py:10
+#: django_authopenid/urls.py:11 django_authopenid/urls.py:13 forum/urls.py:24
+msgid "signin/"
msgstr ""
-#: django_authopenid/forms.py:170 django_authopenid/forms.py:171
-msgid "this name is already in use - please try anoter"
+#: django_authopenid/urls.py:10
+msgid "newquestion/"
msgstr ""
-#: django_authopenid/forms.py:185
-msgid ""
-"This email is already registered in our database. "
-"Please choose another."
+#: django_authopenid/urls.py:11
+msgid "newanswer/"
msgstr ""
-#: django_authopenid/forms.py:216
-msgid ""
-"This username don't exist. Please choose another."
+#: django_authopenid/urls.py:12
+msgid "signout/"
msgstr ""
-#: django_authopenid/forms.py:255
-msgid "choose a username"
+#: django_authopenid/urls.py:13
+msgid "complete/"
msgstr ""
-#: django_authopenid/forms.py:257 templates/authopenid/signup.html:38
-msgid "your email address"
-msgstr ""
+#: django_authopenid/urls.py:15
+msgid "external-login/"
+msgstr "using-nmr-wiki-login-and-password/"
-#: django_authopenid/forms.py:259 templates/authopenid/signup.html:39
-msgid "choose password"
+#: django_authopenid/urls.py:16
+msgid "register/"
msgstr ""
-#: django_authopenid/forms.py:261 templates/authopenid/signup.html:40
-msgid "retype password"
+#: django_authopenid/urls.py:17
+msgid "signup/"
msgstr ""
-#: django_authopenid/forms.py:335
-msgid ""
-"Old password is incorrect. Please enter the correct "
-"password."
+#: django_authopenid/urls.py:19
+msgid "sendpw/"
msgstr ""
-#: django_authopenid/forms.py:347
-msgid "new passwords do not match"
+#: django_authopenid/urls.py:20 django_authopenid/urls.py:24
+msgid "password/"
msgstr ""
-#: django_authopenid/forms.py:442
-msgid "Incorrect username."
+#: django_authopenid/urls.py:20
+msgid "confirm/"
msgstr ""
-#: django_authopenid/urls.py:10 forum/views.py:305 forum/views.py:700
-msgid "newquestion/"
+#: django_authopenid/urls.py:23
+msgid "account_settings"
msgstr ""
-#: django_authopenid/urls.py:11
-msgid "newanswer/"
+#: django_authopenid/urls.py:25 django_authopenid/urls.py:26
+#: django_authopenid/urls.py:27 django_authopenid/urls.py:28
+msgid "email/"
msgstr ""
-#: django_authopenid/urls.py:12
-msgid "signout/"
+#: django_authopenid/urls.py:25
+msgid "validate/"
msgstr ""
-#: django_authopenid/urls.py:13
-msgid "complete/"
+#: django_authopenid/urls.py:26
+msgid "change/"
msgstr ""
-#: django_authopenid/urls.py:15
-msgid "register/"
+#: django_authopenid/urls.py:27
+msgid "sendkey/"
msgstr ""
-#: django_authopenid/urls.py:16
-msgid "signup/"
+#: django_authopenid/urls.py:28
+msgid "verify/"
msgstr ""
-#: django_authopenid/urls.py:18
-msgid "sendpw/"
+#: django_authopenid/urls.py:29
+msgid "openid/"
msgstr ""
-#: django_authopenid/urls.py:27
-msgid "validate/"
+#: django_authopenid/urls.py:30 forum/urls.py:44 forum/urls.py:48
+msgid "delete/"
msgstr ""
-#: django_authopenid/views.py:108
+#: django_authopenid/views.py:124
#, python-format
msgid "OpenID %(openid_url)s is invalid"
msgstr ""
-#: django_authopenid/views.py:418 django_authopenid/views.py:545
-msgid "Welcome"
-msgstr "Verification Email from Q&amp;A forum"
+#: django_authopenid/views.py:532
+msgid "Welcome email subject line"
+msgstr "Welcome to the Q&A forum"
-#: django_authopenid/views.py:508
+#: django_authopenid/views.py:627
msgid "Password changed."
msgstr ""
-#: django_authopenid/views.py:520 django_authopenid/views.py:525
-msgid "your email needs to be validated"
+#: django_authopenid/views.py:639 django_authopenid/views.py:645
+#, python-format
+msgid "your email needs to be validated see %(details_url)s"
msgstr ""
"Your email needs to be validated. Please see details <a "
-"id='validate_email_alert' href=\"/faq#validate\">here</a>."
+"id='validate_email_alert' href='%(details_url)s'>here</a>."
+
+#: django_authopenid/views.py:666
+msgid "Email verification subject line"
+msgstr "Verification Email from Q&A forum"
+
+#: django_authopenid/views.py:752
+msgid "your email was not changed"
+msgstr ""
-#: django_authopenid/views.py:682 django_authopenid/views.py:834
+#: django_authopenid/views.py:799 django_authopenid/views.py:951
#, python-format
msgid "No OpenID %s found associated in our database"
msgstr ""
-#: django_authopenid/views.py:686 django_authopenid/views.py:841
+#: django_authopenid/views.py:803 django_authopenid/views.py:958
#, python-format
msgid "The OpenID %s isn't associated to current user logged in"
msgstr ""
-#: django_authopenid/views.py:694
+#: django_authopenid/views.py:811
msgid "Email Changed."
msgstr ""
-#: django_authopenid/views.py:769
+#: django_authopenid/views.py:886
msgid "This OpenID is already associated with another account."
msgstr ""
-#: django_authopenid/views.py:774
+#: django_authopenid/views.py:891
#, python-format
msgid "OpenID %s is now associated with your account."
msgstr ""
-#: django_authopenid/views.py:844
+#: django_authopenid/views.py:961
msgid "Account deleted."
msgstr ""
-#: django_authopenid/views.py:884
+#: django_authopenid/views.py:1004
msgid "Request for new password"
msgstr ""
-#: django_authopenid/views.py:897
-msgid "A new password has been sent to your email address."
+#: django_authopenid/views.py:1017
+msgid "A new password and the activation link were sent to your email address."
msgstr ""
-#: django_authopenid/views.py:927
+#: django_authopenid/views.py:1047
#, python-format
msgid ""
"Could not change password. Confirmation key '%s' is not "
"registered."
msgstr ""
-#: django_authopenid/views.py:936
+#: django_authopenid/views.py:1056
msgid ""
"Can not change password. User don't exist anymore in our "
"database."
msgstr ""
-#: django_authopenid/views.py:945
+#: django_authopenid/views.py:1065
#, python-format
msgid "Password changed for %s. You may now sign in."
msgstr ""
+#: forum/auth.py:484
+msgid "Your question and all of it's answers have been deleted"
+msgstr ""
+
+#: forum/auth.py:486
+msgid "Your question has been deleted"
+msgstr ""
+
+#: forum/auth.py:489
+msgid "The question and all of it's answers have been deleted"
+msgstr ""
+
+#: forum/auth.py:491
+msgid "The question has been deleted"
+msgstr ""
+
#: forum/const.py:8
msgid "duplicate question"
msgstr ""
#: forum/const.py:9
-msgid "question if off-topic or not relevant"
+msgid "question is off-topic or not relevant"
msgstr ""
#: forum/const.py:10
@@ -375,90 +359,102 @@ msgstr ""
msgid "spam or advertising"
msgstr ""
-#: forum/const.py:56
+#: forum/const.py:57
msgid "question"
msgstr ""
-#: forum/const.py:57 templates/book.html:110
+#: forum/const.py:58 templates/book.html:110
msgid "answer"
msgstr ""
-#: forum/const.py:58
+#: forum/const.py:59
msgid "commented question"
msgstr ""
-#: forum/const.py:59
+#: forum/const.py:60
msgid "commented answer"
msgstr ""
-#: forum/const.py:60
+#: forum/const.py:61
msgid "edited question"
msgstr ""
-#: forum/const.py:61
+#: forum/const.py:62
msgid "edited answer"
msgstr ""
-#: forum/const.py:62
+#: forum/const.py:63
msgid "received award"
msgstr "received badge"
-#: forum/const.py:63
+#: forum/const.py:64
msgid "marked best answer"
msgstr ""
-#: forum/const.py:64
+#: forum/const.py:65
msgid "upvoted"
msgstr ""
-#: forum/const.py:65
+#: forum/const.py:66
msgid "downvoted"
msgstr ""
-#: forum/const.py:66
+#: forum/const.py:67
msgid "canceled vote"
msgstr ""
-#: forum/const.py:67
+#: forum/const.py:68
msgid "deleted question"
msgstr ""
-#: forum/const.py:68
+#: forum/const.py:69
msgid "deleted answer"
msgstr ""
-#: forum/const.py:69
+#: forum/const.py:70
msgid "marked offensive"
msgstr ""
-#: forum/const.py:70
+#: forum/const.py:71
msgid "updated tags"
msgstr ""
-#: forum/const.py:71
+#: forum/const.py:72
msgid "selected favorite"
msgstr ""
-#: forum/const.py:72
+#: forum/const.py:73
msgid "completed user profile"
msgstr ""
-#: forum/const.py:83
+#: forum/const.py:74
+msgid "email update sent to user"
+msgstr ""
+
+#: forum/const.py:85
msgid "[closed]"
msgstr ""
-#: forum/const.py:84
+#: forum/const.py:86
msgid "[deleted]"
msgstr ""
-#: forum/const.py:85
+#: forum/const.py:87 forum/views.py:849 forum/views.py:868
msgid "initial version"
msgstr ""
-#: forum/const.py:86
+#: forum/const.py:88
msgid "retagged"
msgstr ""
+#: forum/const.py:92
+msgid "exclude ignored tags"
+msgstr ""
+
+#: forum/const.py:92
+msgid "allow only interesting tags"
+msgstr ""
+
#: forum/feed.py:18
msgid " - "
msgstr ""
@@ -467,151 +463,399 @@ msgstr ""
msgid "latest questions"
msgstr ""
-#: forum/forms.py:14 templates/answer_edit_tips.html:33
-#: templates/answer_edit_tips.html.py:37 templates/question_edit_tips.html:31
-#: templates/question_edit_tips.html:36
+#: forum/feed.py:19 forum/urls.py:52
+msgid "question/"
+msgstr ""
+
+#: forum/forms.py:16 templates/answer_edit_tips.html:35
+#: templates/answer_edit_tips.html.py:39 templates/question_edit_tips.html:32
+#: templates/question_edit_tips.html:37
msgid "title"
msgstr ""
-#: forum/forms.py:15
+#: forum/forms.py:17
msgid "please enter a descriptive title for your question"
msgstr ""
-#: forum/forms.py:20
+#: forum/forms.py:22
msgid "title must be > 10 characters"
msgstr ""
-#: forum/forms.py:29
+#: forum/forms.py:31
msgid "content"
msgstr ""
-#: forum/forms.py:35
+#: forum/forms.py:37
msgid "question content must be > 10 characters"
msgstr ""
-#: forum/forms.py:45 templates/header.html:30 templates/header.html.py:64
+#: forum/forms.py:47 templates/header.html:28 templates/header.html.py:62
msgid "tags"
msgstr ""
-#: forum/forms.py:47
+#: forum/forms.py:49
msgid ""
"Tags are short keywords, with no spaces within. Up to five tags can be used."
msgstr ""
-#: forum/forms.py:54 templates/question_retag.html:38
+#: forum/forms.py:56 templates/question_retag.html:39
msgid "tags are required"
msgstr ""
-#: forum/forms.py:58
+#: forum/forms.py:62
msgid "please use 5 tags or less"
msgstr ""
-#: forum/forms.py:61
+#: forum/forms.py:65
msgid "tags must be shorter than 20 characters"
msgstr ""
-#: forum/forms.py:65
+#: forum/forms.py:69
msgid ""
"please use following characters in tags: letters 'a-z', numbers, and "
"characters '.-_#'"
msgstr ""
-#: forum/forms.py:75 templates/index.html:57 templates/question.html:209
-#: templates/question.html.py:395 templates/questions.html:58
-#: templates/questions.html.py:70 templates/unanswered.html:48
-#: templates/unanswered.html.py:60
+#: forum/forms.py:79 templates/index.html:61 templates/index.html.py:73
+#: templates/post_contributor_info.html:7
+#: templates/question_summary_list_roll.html:26
+#: templates/question_summary_list_roll.html:38 templates/questions.html:92
+#: templates/questions.html.py:104 templates/unanswered.html:51
+#: templates/unanswered.html.py:63
msgid "community wiki"
msgstr ""
-#: forum/forms.py:76
+#: forum/forms.py:80
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:89
+#: forum/forms.py:96
msgid "update summary:"
msgstr ""
-#: forum/forms.py:90
+#: forum/forms.py:97
msgid ""
"enter a brief summary of your revision (e.g. fixed spelling, grammar, "
"improved style, this field is optional)"
msgstr ""
-#: forum/forms.py:175
+#: forum/forms.py:100
+msgid "Automatically accept user's contributions for the email updates"
+msgstr ""
+
+#: forum/forms.py:113
+msgid "Your name:"
+msgstr ""
+
+#: forum/forms.py:114
+msgid "Email (not shared with anyone):"
+msgstr ""
+
+#: forum/forms.py:115
+msgid "Your message:"
+msgstr ""
+
+#: forum/forms.py:197
msgid "this email does not have to be linked to gravatar"
msgstr ""
-#: forum/forms.py:176
+#: forum/forms.py:198
+msgid "Screen name"
+msgstr ""
+
+#: forum/forms.py:199
msgid "Real name"
msgstr ""
-#: forum/forms.py:177
+#: forum/forms.py:200
msgid "Website"
msgstr ""
-#: forum/forms.py:178
+#: forum/forms.py:201
msgid "Location"
msgstr ""
-#: forum/forms.py:179
+#: forum/forms.py:202
msgid "Date of birth"
msgstr ""
-#: forum/forms.py:179
+#: forum/forms.py:202
msgid "will not be shown, used to calculate age, format: YYYY-MM-DD"
msgstr ""
-#: forum/forms.py:180 templates/authopenid/settings.html:21
+#: forum/forms.py:203 templates/authopenid/settings.html:21
msgid "Profile"
msgstr ""
-#: forum/forms.py:207 forum/forms.py:208
+#: forum/forms.py:231 forum/forms.py:232
msgid "this email has already been registered, please use another one"
msgstr ""
-#: forum/models.py:246
+#: forum/forms.py:238
+msgid "Choose email tag filter"
+msgstr ""
+
+#: forum/forms.py:245 forum/forms.py:246
+msgid "weekly"
+msgstr ""
+
+#: forum/forms.py:245 forum/forms.py:246
+msgid "no email"
+msgstr ""
+
+#: forum/forms.py:246
+msgid "daily"
+msgstr ""
+
+#: forum/forms.py:261
+msgid "Asked by me"
+msgstr ""
+
+#: forum/forms.py:264
+msgid "Answered by me"
+msgstr ""
+
+#: forum/forms.py:267
+msgid "Individually selected"
+msgstr ""
+
+#: forum/forms.py:270
+msgid "Entire forum (tag filtered)"
+msgstr ""
+
+#: forum/models.py:51
+msgid "Entire forum"
+msgstr ""
+
+#: forum/models.py:52
+msgid "Questions that I asked"
+msgstr ""
+
+#: forum/models.py:53
+msgid "Questions that I answered"
+msgstr ""
+
+#: forum/models.py:54
+msgid "Individually selected questions"
+msgstr ""
+
+#: forum/models.py:57
+msgid "Weekly"
+msgstr ""
+
+#: forum/models.py:58
+msgid "Daily"
+msgstr ""
+
+#: forum/models.py:59
+msgid "No email"
+msgstr ""
+
+#: forum/models.py:301
#, python-format
msgid "%(author)s modified the question"
msgstr ""
-#: forum/models.py:250
+#: forum/models.py:305
#, python-format
msgid "%(people)s posted %(new_answer_count)s new answers"
msgstr ""
-#: forum/models.py:255
+#: forum/models.py:310
#, python-format
msgid "%(people)s commented the question"
msgstr ""
-#: forum/models.py:260
+#: forum/models.py:315
#, python-format
msgid "%(people)s commented answers"
msgstr ""
-#: forum/models.py:262
+#: forum/models.py:317
#, python-format
msgid "%(people)s commented an answer"
msgstr ""
-#: forum/models.py:306 forum/models.py:418
-msgid "revisions"
+#: forum/models.py:348
+msgid "interesting"
+msgstr ""
+
+#: forum/models.py:348
+msgid "ignored"
msgstr ""
-#: forum/models.py:441 templates/badges.html:51
+#: forum/models.py:511 templates/badges.html:53
msgid "gold"
msgstr ""
-#: forum/models.py:442 templates/badges.html:59
+#: forum/models.py:512 templates/badges.html:61
msgid "silver"
msgstr ""
-#: forum/models.py:443 templates/badges.html:66
+#: forum/models.py:513 templates/badges.html:68
msgid "bronze"
msgstr ""
+#: forum/urls.py:21
+msgid "upfiles/"
+msgstr ""
+
+#: forum/urls.py:25
+msgid "about/"
+msgstr ""
+
+#: forum/urls.py:26
+msgid "faq/"
+msgstr ""
+
+#: forum/urls.py:27
+msgid "privacy/"
+msgstr ""
+
+#: forum/urls.py:28
+msgid "logout/"
+msgstr ""
+
+#: forum/urls.py:29 forum/urls.py:30 forum/urls.py:31 forum/urls.py:48
+msgid "answers/"
+msgstr ""
+
+#: forum/urls.py:29 forum/urls.py:41 forum/urls.py:44 forum/urls.py:48
+msgid "comments/"
+msgstr ""
+
+#: forum/urls.py:30 forum/urls.py:35 forum/urls.py:70
+#: templates/user_info.html:45
+msgid "edit/"
+msgstr ""
+
+#: forum/urls.py:31 forum/urls.py:40
+msgid "revisions/"
+msgstr ""
+
+#: forum/urls.py:32 forum/urls.py:33 forum/urls.py:34 forum/urls.py:35
+#: forum/urls.py:36 forum/urls.py:37 forum/urls.py:38 forum/urls.py:39
+#: forum/urls.py:40 forum/urls.py:41 forum/urls.py:44
+msgid "questions/"
+msgstr ""
+
+#: forum/urls.py:33 forum/urls.py:80
+msgid "ask/"
+msgstr ""
+
+#: forum/urls.py:34
+msgid "unanswered/"
+msgstr ""
+
+#: forum/urls.py:36
+msgid "close/"
+msgstr ""
+
+#: forum/urls.py:37
+msgid "reopen/"
+msgstr ""
+
+#: forum/urls.py:38
+msgid "answer/"
+msgstr ""
+
+#: forum/urls.py:39
+msgid "vote/"
+msgstr ""
+
+#: forum/urls.py:42
+msgid "command/"
+msgstr ""
+
+#: forum/urls.py:53 forum/urls.py:54
+msgid "tags/"
+msgstr ""
+
+#: forum/urls.py:56 forum/urls.py:60
+msgid "mark-tag/"
+msgstr ""
+
+#: forum/urls.py:56
+msgid "interesting/"
+msgstr ""
+
+#: forum/urls.py:60
+msgid "ignored/"
+msgstr ""
+
+#: forum/urls.py:64
+msgid "unmark-tag/"
+msgstr ""
+
+#: forum/urls.py:68 forum/urls.py:70 forum/urls.py:71
+msgid "users/"
+msgstr ""
+
+#: forum/urls.py:69
+msgid "moderate-user/"
+msgstr ""
+
+#: forum/urls.py:72 forum/urls.py:73
+msgid "badges/"
+msgstr ""
+"Your subscription is saved, but email address %(email)s needs to be "
+"validated, please see <a href='%(details_url)s'>more details here</a>"
+
+#: forum/views.py:1022
+msgid "email update frequency has been set to daily"
+msgstr ""
+
+#: forum/views.py:1826
+msgid "changes saved"
+msgstr ""
+
+#: forum/views.py:1832
+msgid "email updates canceled"
+msgstr ""
+
+#: forum/views.py:1999
+msgid "uploading images is limited to users with >60 reputation points"
+msgstr "sorry, file uploading requires karma >60"
+
+#: forum/urls.py:74
+msgid "messages/"
+msgstr ""
+
+#: forum/urls.py:74
+msgid "markread/"
+msgstr ""
+
+#: forum/urls.py:76
+msgid "nimda/"
+msgstr ""
+
+#: forum/urls.py:78
+msgid "upload/"
+msgstr ""
+
+#: forum/urls.py:79 forum/urls.py:80 forum/urls.py:81
+msgid "books/"
+msgstr ""
+
+#: forum/urls.py:82
+msgid "search/"
+msgstr ""
+"<p>Please remember that you can always <a href='%(link)s'>adjust</a> frequency of the "
+"email updates or turn them off entirely.<br/>If you believe that this message "
+"was sent in an error, please email about it the forum administrator at %(email)"
+"s.</p>"
+"<p>Sincerely,</p><p>Your friendly Q&A forum server.</p>"
+
+#: forum/urls.py:83
+msgid "feedback/"
+msgstr ""
+
+#: forum/urls.py:84
+msgid "account/"
+msgstr ""
+
#: forum/user.py:16 templates/user_tabs.html:7
msgid "overview"
msgstr ""
@@ -648,17 +892,17 @@ msgstr ""
msgid "profile - responses"
msgstr ""
-#: forum/user.py:42 templates/user_info.html:23 templates/users.html:26
+#: forum/user.py:42 templates/user_info.html:22 templates/users.html:26
msgid "reputation"
-msgstr ""
+msgstr "karma"
#: forum/user.py:43
msgid "user reputation in the community"
-msgstr ""
+msgstr "user karma"
#: forum/user.py:44
msgid "profile - user reputation"
-msgstr ""
+msgstr "Profile - User's Karma"
#: forum/user.py:50
msgid "favorite questions"
@@ -684,59 +928,154 @@ msgstr ""
msgid "profile - votes"
msgstr ""
-#: forum/user.py:68
-msgid "preferences"
+#: forum/user.py:68 templates/user_tabs.html:28
+msgid "email subscriptions"
msgstr ""
#: forum/user.py:69 templates/user_tabs.html:27
-msgid "user preference settings"
+msgid "email subscription settings"
msgstr ""
#: forum/user.py:70
-msgid "profile - user preferences"
+msgid "profile - email subscriptions"
+msgstr ""
+
+#: forum/views.py:141
+msgid "Q&A forum feedback"
+msgstr ""
+
+#: forum/views.py:142
+msgid "Thanks for the feedback!"
+msgstr ""
+
+#: forum/views.py:150
+msgid "We look forward to hearing your feedback! Please, give it next time :)"
msgstr ""
-#: forum/views.py:948
+#: forum/views.py:1150
#, python-format
-msgid "subscription saved, %(email)s needs validation"
+msgid "subscription saved, %(email)s needs validation, see %(details_url)s"
msgstr ""
"Your subscription is saved, but email address %(email)s needs to be "
-"validated, please see <a href='/faq#validate'>more details here</a>"
+"validated, please see <a href='%(details_url)s'>more details here</a>"
-#: forum/views.py:1874
-msgid "uploading images is limited to users with >60 reputation points"
+#: forum/views.py:1158
+msgid "email update frequency has been set to daily"
msgstr ""
-#: forum/views.py:1876
+#: forum/views.py:2032
+msgid "changes saved"
+msgstr ""
+
+#: forum/views.py:2038
+msgid "email updates canceled"
+msgstr ""
+
+#: forum/views.py:2207
+msgid "uploading images is limited to users with >60 reputation points"
+msgstr "sorry, file uploading requires karma >60"
+
+#: forum/views.py:2209
msgid "allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"
msgstr ""
-#: forum/views.py:1878
+#: forum/views.py:2211
#, python-format
msgid "maximum upload file size is %sK"
msgstr ""
-#: forum/views.py:1880
+#: forum/views.py:2213
#, 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 "Q&amp;A forum update"
+#: forum/management/commands/send_email_alerts.py:160
+msgid "email update message subject"
+msgstr "news from Q&A forum"
-#: forum/templatetags/extra_tags.py:143 forum/templatetags/extra_tags.py:172
-#: templates/header.html:35
+#: forum/management/commands/send_email_alerts.py:161
+#, python-format
+msgid "%(name)s, this is an update message header for a question"
+msgid_plural "%(name)s, this is an update message header for %(num)d questions"
+msgstr[0] ""
+"<p>Dear %(name)s,</p></p>The following question has been updated on the Q&A "
+"forum:</p>"
+msgstr[1] ""
+"<p>Dear %(name)s,</p><p>The following %(num)d questions have been updated on "
+"the Q&A forum:</p>"
+
+#: forum/management/commands/send_email_alerts.py:172
+msgid "new question"
+msgstr ""
+
+#: forum/management/commands/send_email_alerts.py:182
+#, python-format
+msgid "There is also one question which was recently "
+msgid_plural ""
+"There are also %(num)d more questions which were recently updated "
+msgstr[0] ""
+msgstr[1] ""
+
+#: forum/management/commands/send_email_alerts.py:187
+msgid ""
+"Perhaps you could look up previously sent forum reminders in your mailbox."
+msgstr ""
+
+#: forum/management/commands/send_email_alerts.py:191
+#, python-format
+msgid ""
+"go to %(link)s to change frequency of email updates or %(email)s "
+"administrator"
+msgstr ""
+"<p>Please remember that you can always <a href='%(link)s'>adjust</a> "
+"frequency of the email updates or turn them off entirely.<br/>If you believe "
+"that this message was sent in an error, please email about it the forum "
+"administrator at %(email)s.</p><p>Sincerely,</p><p>Your friendly Q&A forum "
+"server.</p>"
+
+#: forum/templatetags/extra_tags.py:163 forum/templatetags/extra_tags.py:192
+#: templates/header.html:33
msgid "badges"
msgstr ""
-#: forum/templatetags/extra_tags.py:144 forum/templatetags/extra_tags.py:171
+#: forum/templatetags/extra_tags.py:164 forum/templatetags/extra_tags.py:191
msgid "reputation points"
+msgstr "karma"
+
+#: forum/templatetags/extra_tags.py:247
+msgid "%b %d at %H:%M"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:249
+msgid "%b %d '%y at %H:%M"
msgstr ""
-#: forum/templatetags/extra_tags.py:225
-msgid " ago"
+#: forum/templatetags/extra_tags.py:251
+msgid "2 days ago"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:253
+msgid "yesterday"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:255
+#, python-format
+msgid "%(hr)d hour ago"
+msgid_plural "%(hr)d hours ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: forum/templatetags/extra_tags.py:257
+#, python-format
+msgid "%(min)d min ago"
+msgid_plural "%(min)d mins ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: middleware/anon_user.py:33
+#, python-format
+msgid "first time greeting with %(url)s"
msgstr ""
#: templates/404.html:24
@@ -780,6 +1119,11 @@ msgstr ""
#: templates/404.html:43
msgid "see all tags"
msgstr ""
+"<span class=\"strong big\">You are welcome to start submitting your question "
+"anonymously</span>. When you submit the post, you will be redirected to the "
+"login/signup page. Your question will be saved in the current session and "
+"will be published after you log in. Login/signup process is very simple. "
+"Login takes about 30 seconds, initial signup takes a minute or less."
#: templates/500.html:22
msgid "sorry, system error"
@@ -796,6 +1140,11 @@ msgstr ""
#: templates/500.html:28
msgid "see latest questions"
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='%(email_validation_faq_url)s'>more details here</a>."
+"<br>You can submit your question now and validate email after that. Your "
+"question will saved as pending meanwhile. "
#: templates/500.html:29
msgid "see tags"
@@ -805,55 +1154,90 @@ msgstr ""
msgid "About"
msgstr ""
-#: templates/answer_edit.html:4 templates/answer_edit.html.py:47
+#: templates/about.html:21
+msgid ""
+"<strong>CNPROG <span class=\"orange\">Q&amp;A</span></strong> is a "
+"collaboratively edited question\n"
+" and answer site created for the <strong>CNPROG</strong> "
+"community.\n"
+" "
+msgstr ""
+
+#: templates/about.html:25
+msgid ""
+"Here you can <strong>ask</strong> and <strong>answer</strong> questions, "
+"<strong>comment</strong>\n"
+" and <strong>vote</strong> for the questions of others and their answers. "
+"Both questions and answers\n"
+" <strong>can be revised</strong> and improved. Questions can be "
+"<strong>tagged</strong> with\n"
+" the relevant keywords to simplify future access and organize the "
+"accumulated material."
+msgstr ""
+
+#: templates/about.html:31
+msgid ""
+"This <span class=\"orange\">Q&amp;A</span> site is moderated by its members, "
+"hopefully - including yourself!\n"
+" Moderation rights are gradually assigned to the site users based on the "
+"accumulated <strong>\"reputation\"</strong>\n"
+" points. These points are added to the users account when others vote for "
+"his/her questions or answers.\n"
+" These points (very) roughly reflect the level of trust of the community."
+msgstr ""
+
+#: templates/answer_edit.html:5 templates/answer_edit.html.py:48
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:43
-#: templates/question.html.py:46 templates/question_edit.html:27
+#: templates/answer_edit.html:25 templates/answer_edit.html.py:28
+#: templates/ask.html:26 templates/ask.html.py:29 templates/question.html:45
+#: templates/question.html.py:48 templates/question_edit.html:25
+#: templates/question_edit.html.py:28
msgid "hide preview"
msgstr ""
-#: templates/answer_edit.html:27 templates/ask.html:28
-#: templates/question.html:46 templates/question_edit.html:27
+#: templates/answer_edit.html:28 templates/ask.html:29
+#: templates/question.html:48 templates/question_edit.html:28
msgid "show preview"
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/answer_edit.html:47 templates/question_edit.html:65
-#: templates/question_retag.html:52 templates/revisions_answer.html:36
-#: templates/revisions_question.html:36
+#: templates/answer_edit.html:48 templates/question_edit.html:66
+#: templates/question_retag.html:53 templates/revisions_answer.html:38
+#: templates/revisions_question.html:38
msgid "back"
msgstr ""
-#: templates/answer_edit.html:52 templates/question_edit.html:70
-#: templates/revisions_answer.html:47 templates/revisions_question.html:47
+#: templates/answer_edit.html:53 templates/question_edit.html:71
+#: templates/revisions_answer.html:52 templates/revisions_question.html:52
msgid "revision"
msgstr ""
-#: templates/answer_edit.html:55 templates/question_edit.html:74
+#: templates/answer_edit.html:56 templates/question_edit.html:75
msgid "select revision"
msgstr ""
-#: templates/answer_edit.html:62 templates/ask.html:94
-#: templates/question.html:467 templates/question_edit.html:91
+#: templates/answer_edit.html:63 templates/ask.html:97
+#: templates/question.html:434 templates/question_edit.html:92
msgid "Toggle the real time Markdown editor preview"
msgstr ""
-#: templates/answer_edit.html:62 templates/ask.html:94
-#: templates/question.html:467 templates/question_edit.html:91
+#: templates/answer_edit.html:63 templates/ask.html:97
+#: templates/question.html:435 templates/question_edit.html:92
msgid "toggle preview"
msgstr ""
-#: templates/answer_edit.html:71 templates/question_edit.html:115
-#: templates/question_retag.html:73
+#: templates/answer_edit.html:72 templates/question_edit.html:118
+#: templates/question_retag.html:74
msgid "Save edit"
msgstr ""
-#: templates/answer_edit.html:72 templates/close.html:29
-#: templates/question_edit.html:116 templates/question_retag.html:74
-#: templates/reopen.html:30 templates/user_edit.html:83
-#: templates/authopenid/changeemail.html:34
+#: templates/answer_edit.html:73 templates/close.html:29
+#: templates/feedback.html:50 templates/question_edit.html:119
+#: templates/question_retag.html:75 templates/reopen.html:30
+#: templates/user_edit.html:87 templates/authopenid/changeemail.html:40
msgid "Cancel"
msgstr ""
@@ -877,80 +1261,82 @@ msgstr ""
msgid "be clear and concise"
msgstr ""
-#: templates/answer_edit_tips.html:19 templates/question_edit_tips.html:16
+#: templates/answer_edit_tips.html:20 templates/question_edit_tips.html:17
msgid "see frequently asked questions"
msgstr ""
-#: templates/answer_edit_tips.html:24 templates/question_edit_tips.html:22
+#: templates/answer_edit_tips.html:26 templates/question_edit_tips.html:23
msgid "Markdown tips"
msgstr "Markdown basics"
-#: templates/answer_edit_tips.html:27 templates/question_edit_tips.html:25
+#: templates/answer_edit_tips.html:29 templates/question_edit_tips.html:26
msgid "*italic* or __italic__"
msgstr ""
-#: templates/answer_edit_tips.html:30 templates/question_edit_tips.html:28
+#: templates/answer_edit_tips.html:32 templates/question_edit_tips.html:29
msgid "**bold** or __bold__"
msgstr ""
-#: templates/answer_edit_tips.html:33 templates/question_edit_tips.html:31
+#: templates/answer_edit_tips.html:35 templates/question_edit_tips.html:32
msgid "link"
msgstr ""
-#: templates/answer_edit_tips.html:33 templates/answer_edit_tips.html.py:37
-#: templates/question_edit_tips.html:31 templates/question_edit_tips.html:36
+#: templates/answer_edit_tips.html:35 templates/answer_edit_tips.html.py:39
+#: templates/question_edit_tips.html:32 templates/question_edit_tips.html:37
msgid "text"
msgstr ""
-#: templates/answer_edit_tips.html:37 templates/question_edit_tips.html:36
+#: templates/answer_edit_tips.html:39 templates/question_edit_tips.html:37
msgid "image"
msgstr ""
-#: templates/answer_edit_tips.html:41 templates/question_edit_tips.html:40
+#: templates/answer_edit_tips.html:43 templates/question_edit_tips.html:41
msgid "numbered list:"
msgstr ""
-#: templates/answer_edit_tips.html:46 templates/question_edit_tips.html:45
+#: templates/answer_edit_tips.html:48 templates/question_edit_tips.html:46
msgid "basic HTML tags are also supported"
msgstr ""
-#: templates/answer_edit_tips.html:49 templates/question_edit_tips.html:48
+#: templates/answer_edit_tips.html:52 templates/question_edit_tips.html:50
msgid "learn more about Markdown"
msgstr ""
-#: templates/ask.html:4 templates/ask.html.py:60
+#: templates/ask.html:5 templates/ask.html.py:61
msgid "Ask a question"
msgstr ""
-#: templates/ask.html:67
+#: templates/ask.html:68
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."
+"anonymously</span>. When you submit the post, you will be redirected to the "
+"login/signup page. Your question will be saved in the current session and "
+"will be published after 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
+#: templates/ask.html:74
#, python-format
-msgid "must have valid %(email)s to post"
+msgid ""
+"must have valid %(email)s to post, \n"
+" see %(email_validation_faq_url)s\n"
+" "
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. "
+"please see <a href='%(email_validation_faq_url)s'>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:107
+#: templates/ask.html:112
msgid "(required)"
msgstr ""
-#: templates/ask.html:114
+#: templates/ask.html:119
msgid "Login/signup to post your question"
msgstr "Login/Signup to Post"
-#: templates/ask.html:116
+#: templates/ask.html:121
msgid "Ask your question"
msgstr "Ask Your Question"
@@ -966,7 +1352,7 @@ msgstr ""
msgid "Badges summary"
msgstr ""
-#: templates/badges.html:17 templates/user_stats.html:73
+#: templates/badges.html:17
msgid "Badges"
msgstr ""
@@ -977,38 +1363,41 @@ msgstr ""
"Q&amp;A community will be recognized with the variety of badges."
#: templates/badges.html:22
+#, python-format
msgid ""
-"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 \n"
+" of times each type of badge has been awarded. Give us feedback at %"
+"(feedback_faq_url)s.\n"
+" "
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."
+"each level. <strong>Please give us your <a href='%(feedback_faq_url)"
+"s'>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:48
+#: templates/badges.html:50
msgid "Community badges"
msgstr "Badge levels"
-#: templates/badges.html:54
+#: templates/badges.html:56
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:62
+#: templates/badges.html:64
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/badges.html:65
+#: templates/badges.html:67
msgid "bronze badge: often given as a special honor"
msgstr ""
-#: templates/badges.html:69
+#: templates/badges.html:71
msgid "bronze badge description"
msgstr ""
"If you are an active participant in this community, you will be recognized "
@@ -1080,8 +1469,9 @@ msgstr ""
msgid "number of times"
msgstr ""
-#: templates/book.html:105 templates/index.html:48 templates/questions.html:46
-#: templates/unanswered.html:37 templates/users_questions.html:32
+#: templates/book.html:105 templates/index.html:49
+#: templates/question_summary_list_roll.html:14 templates/questions.html:80
+#: templates/unanswered.html:39 templates/users_questions.html:32
msgid "votes"
msgstr ""
@@ -1089,15 +1479,17 @@ msgstr ""
msgid "the answer has been accepted to be correct"
msgstr ""
-#: templates/book.html:115 templates/index.html:49 templates/questions.html:47
-#: templates/unanswered.html:38 templates/users_questions.html:42
+#: templates/book.html:115 templates/index.html:50
+#: templates/question_summary_list_roll.html:15 templates/questions.html:81
+#: templates/unanswered.html:40 templates/users_questions.html:40
msgid "views"
msgstr ""
-#: templates/book.html:125 templates/index.html:69 templates/question.html:499
-#: templates/questions.html:84 templates/questions.html.py:156
-#: templates/tags.html:49 templates/unanswered.html:75
-#: templates/unanswered.html.py:106 templates/users_questions.html:54
+#: templates/book.html:125 templates/index.html:105
+#: templates/question.html:480 templates/question_summary_list_roll.html:52
+#: templates/questions.html:136 templates/tags.html:49
+#: templates/unanswered.html:95 templates/unanswered.html.py:122
+#: templates/users_questions.html:52
msgid "using tags"
msgstr ""
@@ -1105,7 +1497,7 @@ msgstr ""
msgid "subscribe to book RSS feed"
msgstr ""
-#: templates/book.html:147 templates/index.html:118
+#: templates/book.html:147 templates/index.html:156
msgid "subscribe to the questions feed"
msgstr ""
@@ -1189,16 +1581,18 @@ msgid ""
"The reputation system allows users earn the authorization to perform a "
"variety of moderation tasks."
msgstr ""
+"Karma system allows users to earn rights to perform a variety of moderation "
+"tasks"
#: templates/faq.html:40
msgid "How does reputation system work?"
-msgstr ""
+msgstr "How does karma system work?"
#: 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 "
+"some points, which are called \"karma 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."
@@ -1213,7 +1607,7 @@ msgid ""
"type of moderation task."
msgstr ""
-#: templates/faq.html:53 templates/user_votes.html:14
+#: templates/faq.html:53 templates/user_votes.html:15
msgid "upvote"
msgstr ""
@@ -1225,7 +1619,7 @@ msgstr ""
msgid "add comments"
msgstr ""
-#: templates/faq.html:66 templates/user_votes.html:16
+#: templates/faq.html:66 templates/user_votes.html:17
msgid "downvote"
msgstr ""
@@ -1237,51 +1631,53 @@ msgstr ""
msgid "retag questions"
msgstr ""
-#: templates/faq.html:77
+#: templates/faq.html:78
msgid "edit community wiki questions"
msgstr ""
-#: templates/faq.html:81
+#: templates/faq.html:83
msgid "edit any answer"
msgstr ""
-#: templates/faq.html:85
+#: templates/faq.html:87
msgid "open any closed question"
msgstr ""
-#: templates/faq.html:89
+#: templates/faq.html:91
msgid "delete any comment"
msgstr ""
-#: templates/faq.html:93
+#: templates/faq.html:95
msgid "delete any questions and answers and perform other moderation tasks"
msgstr ""
-#: templates/faq.html:100
+#: templates/faq.html:102
msgid "how to validate email title"
msgstr "How to validate email and why?"
-#: templates/faq.html:102
-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:106
+#: templates/faq.html:104
+#, python-format
+msgid ""
+"how to validate email info with %(send_email_key_url)s %(gravatar_faq_url)s"
+msgstr ""
+"<form style='margin:0;padding:0;' action='%(send_email_key_url)s'><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='%"
+"(gravatar_faq_url)s'><strong>gravatar</strong></a> personal image.</p>"
+
+#: templates/faq.html:108
msgid "what is gravatar"
msgstr "What is gravatar?"
-#: templates/faq.html:107
+#: templates/faq.html:109
msgid "gravatar faq info"
msgstr ""
"<strong>Gravatar</strong> means <strong>g</strong>lobally <strong>r</"
@@ -1292,238 +1688,308 @@ msgstr ""
"your image</strong> at <a href='http://gravatar.com'><strong>gravatar.com</"
"strong></a>"
-#: templates/faq.html:110
+#: templates/faq.html:112
msgid "To register, do I need to create new password?"
msgstr ""
-#: templates/faq.html:111
+#: templates/faq.html:113
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:112
+#: templates/faq.html:114
msgid "Login now!"
msgstr ""
-#: templates/faq.html:117
+#: templates/faq.html:119
msgid "Why other people can edit my questions/answers?"
msgstr ""
-#: templates/faq.html:118
+#: templates/faq.html:120
msgid "Goal of this site is..."
msgstr ""
-#: templates/faq.html:118
+#: templates/faq.html:120
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:119
+#: templates/faq.html:121
msgid "If this approach is not for you, we respect your choice."
msgstr ""
-#: templates/faq.html:123
+#: templates/faq.html:125
msgid "Still have questions?"
msgstr ""
-#: templates/faq.html:124
-msgid "Please ask your question, help make our community better!"
+#: templates/faq.html:126
+#, python-format
+msgid ""
+"Please ask your question at %(ask_question_url)s, help make our community "
+"better!"
msgstr ""
-"Please <a href=\"/questions/ask\">ask</a> your question, help make our "
+"Please <a href='%(ask_question_url)s'>ask</a> your question, help make our "
"community better!"
-#: templates/faq.html:126 templates/header.html:29 templates/header.html.py:63
+#: templates/faq.html:128 templates/header.html:27 templates/header.html.py:61
msgid "questions"
msgstr ""
-#: templates/faq.html:126 templates/index.html:123
+#: templates/faq.html:128 templates/index.html:161
msgid "."
msgstr ""
-#: templates/footer.html:8 templates/header.html:14 templates/index.html:83
+#: templates/feedback.html:6
+msgid "Feedback"
+msgstr ""
+
+#: templates/feedback.html:11
+msgid "Give us your feedback!"
+msgstr ""
+
+#: templates/feedback.html:17
+#, python-format
+msgid ""
+"\n"
+" <span class='big strong'>Dear %(user_name)s</span>, we look "
+"forward to hearing your feedback. \n"
+" Please type and send us your message below.\n"
+" "
+msgstr ""
+
+#: templates/feedback.html:24
+msgid ""
+"\n"
+" <span class='big strong'>Dear visitor</span>, we look forward to "
+"hearing your feedback.\n"
+" Please type and send us your message below.\n"
+" "
+msgstr ""
+
+#: templates/feedback.html:41
+msgid "(this field is required)"
+msgstr ""
+
+#: templates/feedback.html:49
+msgid "Send Feedback"
+msgstr ""
+
+#: templates/feedback_email.txt:3
+#, python-format
+msgid ""
+"\n"
+"Hello, this is a %(site_title)s forum feedback message\n"
+msgstr ""
+
+#: templates/feedback_email.txt:9
+msgid "Sender is"
+msgstr ""
+
+#: templates/feedback_email.txt:11 templates/feedback_email.txt.py:14
+msgid "email"
+msgstr ""
+
+#: templates/feedback_email.txt:13
+msgid "anonymous"
+msgstr ""
+
+#: templates/feedback_email.txt:19
+msgid "Message body:"
+msgstr ""
+
+#: templates/footer.html:8 templates/header.html:13 templates/index.html:119
msgid "about"
msgstr ""
-#: templates/footer.html:9 templates/header.html:15 templates/index.html:84
-#: templates/question_edit_tips.html:16
+#: templates/footer.html:9 templates/header.html:14 templates/index.html:120
+#: templates/question_edit_tips.html:17
msgid "faq"
msgstr ""
#: templates/footer.html:10
-msgid "wiki"
-msgstr ""
-
-#: templates/footer.html:11
msgid "blog"
msgstr ""
-#: templates/footer.html:12
+#: templates/footer.html:11
msgid "contact us"
msgstr ""
-#: templates/footer.html:13
+#: templates/footer.html:12
msgid "privacy policy"
msgstr ""
-#: templates/footer.html:14
+#: templates/footer.html:21
msgid "give feedback"
msgstr ""
-#: templates/header.html:10
+#: templates/header.html:9
msgid "logout"
msgstr ""
-#: templates/header.html:12 templates/authopenid/signup.html:41
+#: templates/header.html:11
msgid "login"
msgstr ""
-#: templates/header.html:23
+#: templates/header.html:21
msgid "back to home page"
msgstr ""
-#: templates/header.html:31 templates/header.html.py:65
+#: templates/header.html:29 templates/header.html.py:63
msgid "users"
msgstr ""
-#: templates/header.html:33
+#: templates/header.html:31
msgid "books"
msgstr ""
-#: templates/header.html:36
+#: templates/header.html:34
msgid "unanswered questions"
msgstr "unanswered"
-#: templates/header.html:40
+#: templates/header.html:38
msgid "my profile"
msgstr ""
-#: templates/header.html:44
+#: templates/header.html:42
msgid "ask a question"
msgstr ""
-#: templates/header.html:59
+#: templates/header.html:57
msgid "search"
msgstr ""
-#: templates/index.html:7
+#: templates/index.html:8
msgid "Home"
msgstr ""
-#: templates/index.html:22 templates/questions.html:7
+#: templates/index.html:25 templates/questions.html:8
msgid "Questions"
msgstr ""
-#: templates/index.html:24
+#: templates/index.html:27
msgid "last updated questions"
msgstr ""
-#: templates/index.html:24 templates/questions.html:25
-#: templates/unanswered.html:20
+#: templates/index.html:27 templates/questions.html:47
+#: templates/unanswered.html:21
msgid "newest"
msgstr ""
-#: templates/index.html:25 templates/questions.html:27
+#: templates/index.html:28 templates/questions.html:49
msgid "hottest questions"
msgstr ""
-#: templates/index.html:25 templates/questions.html:27
+#: templates/index.html:28 templates/questions.html:49
msgid "hottest"
msgstr ""
-#: templates/index.html:26 templates/questions.html:28
+#: templates/index.html:29 templates/questions.html:50
msgid "most voted questions"
msgstr ""
-#: templates/index.html:26 templates/questions.html:28
+#: templates/index.html:29 templates/questions.html:50
msgid "most voted"
msgstr ""
-#: templates/index.html:27
+#: templates/index.html:30
msgid "all questions"
msgstr ""
-#: templates/index.html:47 templates/questions.html:45
-#: templates/unanswered.html:36 templates/users_questions.html:35
+#: templates/index.html:48 templates/question_summary_list_roll.html:13
+#: templates/questions.html:79 templates/unanswered.html:38
+#: templates/users_questions.html:36
msgid "answers"
msgstr ""
-#: templates/index.html:69 templates/question.html:499
-#: templates/questions.html:84 templates/questions.html.py:156
-#: templates/tags.html:49 templates/unanswered.html:75
-#: templates/unanswered.html.py:106 templates/users_questions.html:52
+#: templates/index.html:80 templates/index.html.py:94
+#: templates/questions.html:111 templates/questions.html.py:125
+#: templates/unanswered.html:70 templates/unanswered.html.py:84
+msgid "Posted:"
+msgstr ""
+
+#: templates/index.html:83 templates/index.html.py:88
+#: templates/questions.html:114 templates/questions.html.py:119
+#: templates/unanswered.html:73 templates/unanswered.html.py:78
+msgid "Updated:"
+msgstr ""
+
+#: templates/index.html:105 templates/question.html:480
+#: templates/question_summary_list_roll.html:52 templates/questions.html:136
+#: templates/tags.html:49 templates/unanswered.html:95
+#: templates/unanswered.html.py:122 templates/users_questions.html:52
msgid "see questions tagged"
msgstr ""
-#: templates/index.html:80
+#: templates/index.html:116
msgid "welcome to website"
-msgstr ""
+msgstr "Welcome to Q&amp;A forum"
-#: templates/index.html:89
+#: templates/index.html:127
msgid "Recent tags"
msgstr ""
-#: templates/index.html:94 templates/question.html:125
+#: templates/index.html:132 templates/question.html:135
#, python-format
msgid "see questions tagged '%(tagname)s'"
msgstr ""
-#: templates/index.html:97 templates/index.html.py:123
+#: templates/index.html:135 templates/index.html.py:161
msgid "popular tags"
msgstr "tags"
-#: templates/index.html:102
+#: templates/index.html:140
msgid "Recent awards"
msgstr "Recent badges"
-#: templates/index.html:108
+#: templates/index.html:146
msgid "given to"
msgstr ""
-#: templates/index.html:113
+#: templates/index.html:151
msgid "all awards"
msgstr "all badges"
-#: templates/index.html:118
+#: templates/index.html:156
msgid "subscribe to last 30 questions by RSS"
msgstr ""
-#: templates/index.html:123
+#: templates/index.html:161
msgid "Still looking for more? See"
msgstr ""
-#: templates/index.html:123
+#: templates/index.html:161
msgid "complete list of questions"
msgstr "list of all questions"
-#: templates/index.html:123
+#: templates/index.html:161 templates/authopenid/signup.html:18
msgid "or"
msgstr ""
-#: templates/index.html:123
+#: templates/index.html:161
msgid "Please help us answer"
msgstr ""
-#: templates/index.html:123
+#: templates/index.html:161
msgid "list of unanswered questions"
msgstr "unanswered questions"
-#: templates/logout.html:6 templates/logout.html.py:17
+#: templates/logout.html:6 templates/logout.html.py:16
msgid "Logout"
msgstr ""
-#: templates/logout.html:20
+#: templates/logout.html:19
msgid ""
"As a registered user you can login with your OpenID, log out of the site or "
"permanently remove your account."
msgstr ""
-"Clicking <strong>Logout</strong> will log you out from the Q&amp;A forum.</"
-"p><p>If you wish to sign off completely - please make sure to log out from "
-"your OpenID provider."
+"Clicking <strong>Logout</strong> will log you out from the forumbut will not "
+"sign you off from your OpenID provider.</p><p>If you wish to sign off "
+"completely - please make sure to log out from your OpenID provider as well."
-#: templates/logout.html:21
+#: templates/logout.html:20
msgid "Logout now"
msgstr "Logout Now"
@@ -1551,6 +2017,35 @@ msgstr ""
msgid "next page"
msgstr ""
+#: templates/post_contributor_info.html:9
+#, python-format
+msgid ""
+"\n"
+" one revision\n"
+" "
+msgid_plural ""
+"\n"
+" %(rev_count)s revisions\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/post_contributor_info.html:19
+msgid "asked"
+msgstr ""
+
+#: templates/post_contributor_info.html:22
+msgid "answered"
+msgstr ""
+
+#: templates/post_contributor_info.html:24
+msgid "posted"
+msgstr ""
+
+#: templates/post_contributor_info.html:45
+msgid "updated"
+msgstr ""
+
#: templates/privacy.html:6 templates/privacy.html.py:11
msgid "Privacy policy"
msgstr ""
@@ -1558,6 +2053,9 @@ msgstr ""
#: templates/privacy.html:15
msgid "general message about privacy"
msgstr ""
+"Respecting users privacy is an important core principle of this Q&amp;A "
+"forum. Information on this page details how this forum protects your "
+"privacy, and what type of information is collected."
#: templates/privacy.html:18
msgid "Site Visitors"
@@ -1566,6 +2064,9 @@ msgstr ""
#: templates/privacy.html:20
msgid "what technical information is collected about visitors"
msgstr ""
+"Information on question views, revisions of questions and answers - both "
+"times and content are recorded for each user in order to correctly count "
+"number of views, maintain data integrity and report relevant updates."
#: templates/privacy.html:23
msgid "Personal Information"
@@ -1574,6 +2075,9 @@ msgstr ""
#: templates/privacy.html:25
msgid "details on personal information policies"
msgstr ""
+"Members of this community may choose to display personally identifiable "
+"information in their profiles. Forum will never display such information "
+"without a request from the user."
#: templates/privacy.html:28
msgid "Other Services"
@@ -1582,10 +2086,15 @@ msgstr ""
#: templates/privacy.html:30
msgid "details on sharing data with third parties"
msgstr ""
+"None of the data that is not openly shown on the forum by the choice of the "
+"user is shared with any third party."
#: templates/privacy.html:35
msgid "cookie policy details"
msgstr ""
+"Forum software relies on the internet cookie technology to keep track of "
+"user sessions. Cookies must be enabled in your browser so that forum can "
+"work for you."
#: templates/privacy.html:37
msgid "Policy Changes"
@@ -1594,194 +2103,305 @@ msgstr ""
#: templates/privacy.html:38
msgid "how privacy policies can be changed"
msgstr ""
+"These policies may be adjusted to improve protection of user's privacy. "
+"Whenever such changes occur, users will be notified via the internal "
+"messaging system. "
-#: templates/question.html:72 templates/question.html.py:73
-#: templates/question.html:85 templates/question.html.py:87
+#: templates/question.html:77 templates/question.html.py:78
+#: templates/question.html:94 templates/question.html.py:96
msgid "i like this post (click again to cancel)"
msgstr ""
-#: templates/question.html:75 templates/question.html.py:89
-#: templates/question.html:289
+#: templates/question.html:80 templates/question.html.py:98
+#: templates/question.html:257
msgid "current number of votes"
msgstr ""
-#: templates/question.html:80 templates/question.html.py:81
-#: templates/question.html:94 templates/question.html.py:95
+#: templates/question.html:89 templates/question.html.py:90
+#: templates/question.html:103 templates/question.html.py:104
msgid "i dont like this post (click again to cancel)"
msgstr ""
-#: templates/question.html:100 templates/question.html.py:101
+#: templates/question.html:109 templates/question.html.py:110
msgid "mark this question as favorite (click again to cancel)"
msgstr ""
-#: templates/question.html:107 templates/question.html.py:108
+#: templates/question.html:116 templates/question.html.py:117
msgid "remove favorite mark from this question (click again to restore mark)"
msgstr ""
-#: templates/question.html:134 templates/question.html.py:322
-#: templates/revisions_answer.html:53 templates/revisions_question.html:53
+#: templates/question.html:140 templates/question.html.py:294
+#: templates/revisions_answer.html:58 templates/revisions_question.html:58
msgid "edit"
msgstr ""
-#: templates/question.html:138 templates/question.html.py:332
-msgid "delete"
-msgstr ""
-
-#: templates/question.html:143
+#: templates/question.html:145
msgid "reopen"
msgstr ""
-#: templates/question.html:148
+#: templates/question.html:149
msgid "close"
msgstr ""
-#: templates/question.html:154 templates/question.html.py:345
+#: templates/question.html:155 templates/question.html.py:299
msgid ""
"report as offensive (i.e containing spam, advertising, malicious text, etc.)"
msgstr ""
-#: templates/question.html:155 templates/question.html.py:346
+#: templates/question.html:156 templates/question.html.py:300
msgid "flag offensive"
msgstr ""
-#: templates/question.html:167 templates/question.html.py:355
-#: templates/revisions_answer.html:65 templates/revisions_question.html:65
-msgid "updated"
-msgstr ""
-
-#: templates/question.html:216 templates/question.html.py:402
-#: templates/revisions_answer.html:63 templates/revisions_question.html:63
-msgid "asked"
+#: templates/question.html:164 templates/question.html.py:311
+msgid "delete"
msgstr ""
-#: templates/question.html:246 templates/question.html.py:429
-msgid "comments"
+#: templates/question.html:182 templates/question.html.py:331
+msgid "delete this comment"
msgstr ""
-#: templates/question.html:247 templates/question.html.py:430
+#: templates/question.html:193 templates/question.html.py:342
msgid "add comment"
-msgstr ""
+msgstr "post a comment"
+
+#: templates/question.html:197
+#, python-format
+msgid ""
+"\n"
+" see <strong>one</strong> more \n"
+" "
+msgid_plural ""
+"\n"
+" see <strong>%(counter)s</strong> "
+"more\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/question.html:203
+#, python-format
+msgid ""
+"\n"
+" see <strong>one</strong> more "
+"comment\n"
+" "
+msgid_plural ""
+"\n"
+" see <strong>%(counter)s</strong> "
+"more comments\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/question.html:260
+#: templates/question.html:219
#, python-format
msgid ""
-"The question has been closed for the following reason \"%(question."
-"get_close_reason_display)s\" by"
+"The question has been closed for the following reason \"%(close_reason)s\" by"
msgstr ""
-#: templates/question.html:262
+#: templates/question.html:221
#, python-format
-msgid "close date %(question.closed_at)s"
+msgid "close date %(closed_at)s"
msgstr ""
-#: templates/question.html:269 templates/user_stats.html:13
-msgid "Answers"
-msgstr " Answers"
+#: templates/question.html:229
+#, python-format
+msgid ""
+"\n"
+" One Answer:\n"
+" "
+msgid_plural ""
+"\n"
+" %(counter)s Answers:\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/question.html:271
+#: templates/question.html:237
msgid "oldest answers will be shown first"
msgstr ""
-#: templates/question.html:271
+#: templates/question.html:237
msgid "oldest answers"
msgstr "oldest"
-#: templates/question.html:272
+#: templates/question.html:239
msgid "newest answers will be shown first"
msgstr ""
-#: templates/question.html:272
+#: templates/question.html:239
msgid "newest answers"
msgstr "newest"
-#: templates/question.html:273
+#: templates/question.html:241
msgid "most voted answers will be shown first"
msgstr ""
-#: templates/question.html:273
+#: templates/question.html:241
msgid "popular answers"
msgstr "most voted"
-#: templates/question.html:287 templates/question.html.py:288
+#: templates/question.html:255 templates/question.html.py:256
msgid "i like this answer (click again to cancel)"
msgstr ""
-#: templates/question.html:294 templates/question.html.py:295
+#: templates/question.html:262 templates/question.html.py:263
msgid "i dont like this answer (click again to cancel)"
msgstr ""
-#: templates/question.html:300 templates/question.html.py:301
+#: templates/question.html:268 templates/question.html.py:269
msgid "mark this answer as favorite (click again to undo)"
msgstr ""
-#: templates/question.html:306 templates/question.html.py:307
+#: templates/question.html:274 templates/question.html.py:275
msgid "the author of the question has selected this answer as correct"
msgstr ""
-#: templates/question.html:329
+#: templates/question.html:288
+msgid "answer permanent link"
+msgstr ""
+
+#: templates/question.html:289
+msgid "permanent link"
+msgstr "link"
+
+#: templates/question.html:311
msgid "undelete"
msgstr ""
-#: templates/question.html:339
-msgid "answer permanent link"
+#: templates/question.html:346
+#, python-format
+msgid ""
+"\n"
+" see <strong>one</"
+"strong> more \n"
+" "
+msgid_plural ""
+"\n"
+" see <strong>%"
+"(counter)s</strong> more\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/question.html:352
+#, python-format
+msgid ""
+"\n"
+" see <strong>one</"
+"strong> more comment\n"
+" "
+msgid_plural ""
+"\n"
+" see <strong>%"
+"(counter)s</strong> more comments\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/question.html:378 templates/question.html.py:381
+msgid "Notify me once a day when there are any new answers"
msgstr ""
+"<strong>Notify me</strong> once a day by email when there are any new "
+"answers or updates"
-#: templates/question.html:340
-msgid "permanent link"
+#: templates/question.html:384
+msgid "Notify me weekly when there are any new answers"
+msgstr ""
+"<strong>Notify me</strong> weekly when there are any new answers or updates"
+
+#: templates/question.html:389
+#, python-format
+msgid ""
+"\n"
+" You can always adjust frequency of email updates from your %"
+"(profile_url)s\n"
+" "
msgstr ""
+"\n"
+"(note: you can always <a href='%(profile_url)s?"
+"sort=email_subscriptions'>adjust frequency</a> of email updates)"
+
+#: templates/question.html:396
+msgid "once you sign in you will be able to subscribe for any updates here"
+msgstr ""
+"<span class='strong'>Here</span> (once you log in) you will be able to sign "
+"up for the periodic email updates about this question."
-#: templates/question.html:453
+#: templates/question.html:407
msgid "Your answer"
msgstr ""
-#: templates/question.html:456
+#: templates/question.html:409
+msgid "Be the first one to answer this question!"
+msgstr ""
+
+#: templates/question.html:415
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"
+"<span class='strong big'>Please start posting your answer anonymously</span> "
+"- your answer will be saved within the current session and published after "
+"you log in or create a new account. Please try to give a <strong>substantial "
+"answer</strong>, for discussions, <strong>please use comments</strong> and "
+"<strong>please do remember to vote</strong> (after you log in)!"
-#: templates/question.html:479
-msgid "Answer the question"
+#: templates/question.html:419
+msgid "answer your own question only to give an answer"
msgstr ""
+"<span class='big strong'>You are welcome to answer your own question</span>, "
+"but please make sure to give an <strong>answer</strong>. Remember that you "
+"can always <strong>revise your original question</strong>. Please "
+"<strong>use comments for discussions</strong> and <strong>please don't "
+"forget to vote :)</strong> for the answers that you liked (or perhaps did "
+"not like)! "
-#: templates/question.html:481
-msgid "Notify me daily if there are any new answers."
+#: templates/question.html:421
+msgid "please only give an answer, no discussions"
msgstr ""
+"<span class='big strong'>Please try to give a substantial answer</span>. If "
+"you wanted to comment on the question or answer, just <strong>use the "
+"commenting tool</strong>. Please remember that you can always <strong>revise "
+"your answers</strong> - no need to answer the same question twice. Also, "
+"please <strong>don't forget to vote</strong> - it really helps to select the "
+"best questions and answers!"
-#: templates/question.html:483
-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:457
+msgid "Login/Signup to Post Your Answer"
+msgstr ""
+
+#: templates/question.html:460
+msgid "Answer Your Own Question"
+msgstr ""
+
+#: templates/question.html:462
+msgid "Answer the question"
+msgstr "Post Your Answer"
-#: templates/question.html:494
+#: templates/question.html:475
msgid "Question tags"
msgstr "Tags"
-#: templates/question.html:504
+#: templates/question.html:485
msgid "question asked"
msgstr "Asked"
-#: templates/question.html:504 templates/question.html.py:510
-#: templates/user_info.html:51
-msgid "ago"
-msgstr ""
-
-#: templates/question.html:507
+#: templates/question.html:488
msgid "question was seen"
msgstr "Seen"
-#: templates/question.html:507
+#: templates/question.html:488
msgid "times"
msgstr ""
-#: templates/question.html:510
+#: templates/question.html:491
msgid "last updated"
msgstr "Last updated"
-#: templates/question.html:515
+#: templates/question.html:496
msgid "Related questions"
msgstr ""
-#: templates/question_edit.html:4 templates/question_edit.html.py:65
+#: templates/question_edit.html:5 templates/question_edit.html.py:66
msgid "Edit question"
msgstr ""
@@ -1791,66 +2411,87 @@ msgstr "Tips"
#: templates/question_edit_tips.html:7
msgid "please ask a relevant question"
-msgstr ""
+msgstr "ask a question relevant to the CNPROG community"
#: templates/question_edit_tips.html:10
msgid "please try provide enough details"
msgstr "provide enough details"
-#: templates/question_retag.html:3 templates/question_retag.html.py:52
+#: templates/question_retag.html:4 templates/question_retag.html.py:53
msgid "Change tags"
msgstr ""
-#: templates/question_retag.html:39
+#: templates/question_retag.html:40
msgid "up to 5 tags, less than 20 characters each"
msgstr ""
-#: templates/question_retag.html:82
+#: templates/question_retag.html:83
msgid "Why use and modify tags?"
msgstr ""
-#: templates/question_retag.html:85
+#: templates/question_retag.html:86
msgid "tags help us keep Questions organized"
msgstr ""
-#: templates/question_retag.html:91
+#: templates/question_retag.html:94
msgid "tag editors receive special awards from the community"
msgstr ""
-#: templates/questions.html:23
+#: templates/questions.html:29
msgid "Found by tags"
msgstr "Tagged questions"
-#: templates/questions.html:23
+#: templates/questions.html:33
+msgid "Search results"
+msgstr ""
+
+#: templates/questions.html:35
msgid "Found by title"
msgstr ""
-#: templates/questions.html:23
+#: templates/questions.html:39 templates/unanswered.html:8
+#: templates/unanswered.html.py:19
+msgid "Unanswered questions"
+msgstr ""
+
+#: templates/questions.html:41
msgid "All questions"
msgstr ""
-#: templates/questions.html:25 templates/unanswered.html:20
+#: templates/questions.html:47 templates/unanswered.html:21
msgid "most recently asked questions"
msgstr ""
-#: templates/questions.html:26
+#: templates/questions.html:48
msgid "most recently updated questions"
msgstr ""
-#: templates/questions.html:26
+#: templates/questions.html:48
msgid "active"
msgstr ""
-#: templates/questions.html:109
+#: templates/questions.html:144
+msgid "Did not find anything?"
+msgstr ""
+
+#: templates/questions.html:147
+msgid "Did not find what you were looking for?"
+msgstr ""
+
+#: templates/questions.html:149
+msgid "Please, post your question!"
+msgstr ""
+
+#: templates/questions.html:163
#, python-format
msgid ""
"\n"
-"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
-"\t\t\t"
+" have total %(q_num)s questions tagged %(tagname)s\n"
+" "
msgid_plural ""
"\n"
-"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
-"\t\t\t"
+" have total %(q_num)s questions tagged %(tagname)s\n"
+" "
msgstr[0] ""
"\n"
"<div class=\"questions-count\">%(q_num)s</div><p>question tagged</p><p><span "
@@ -1860,16 +2501,41 @@ msgstr[1] ""
"<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
+#: templates/questions.html:171
#, python-format
msgid ""
"\n"
-"\t\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
-"\t\t\t\t"
+" have total %(q_num)s questions containing %(searchtitle)"
+"s in full text\n"
+" "
msgid_plural ""
"\n"
-"\t\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
-"\t\t\t\t"
+" have total %(q_num)s questions containing %(searchtitle)"
+"s in full text\n"
+" "
+msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question "
+"containing <strong><span class=\"darkred\">%(searchtitle)s</span></strong></"
+"p>"
+msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions "
+"containing <strong><span class=\"darkred\">%(searchtitle)s</span></strong></"
+"p>"
+
+#: templates/questions.html:177
+#, python-format
+msgid ""
+"\n"
+" have total %(q_num)s questions containing %(searchtitle)"
+"s\n"
+" "
+msgid_plural ""
+"\n"
+" have total %(q_num)s questions containing %(searchtitle)"
+"s\n"
+" "
msgstr[0] ""
"\n"
"<div class=\"questions-count\">%(q_num)s</div><p>question with title "
@@ -1881,55 +2547,81 @@ msgstr[1] ""
"containing <strong><span class=\"darkred\">%(searchtitle)s</span></strong></"
"p>"
-#: templates/questions.html:122
+#: templates/questions.html:185
#, python-format
msgid ""
"\n"
-"\t\t\t\thave total %(q_num)s questions\n"
-"\t\t\t\t"
+" have total %(q_num)s unanswered questions\n"
+" "
msgid_plural ""
"\n"
-"\t\t\t\thave total %(q_num)s questions\n"
-"\t\t\t\t"
+" have total %(q_num)s unanswered questions\n"
+" "
+msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question without an "
+"accepted answer</p>"
+msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions without an "
+"accepted answer</p>"
+
+#: templates/questions.html:191
+#, python-format
+msgid ""
+"\n"
+" have total %(q_num)s questions\n"
+" "
+msgid_plural ""
+"\n"
+" have total %(q_num)s questions\n"
+" "
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:131
+"<div class=\"questions-count\">%(q_num)s</div><p>questions<p>"
+
+#: templates/questions.html:201
msgid "latest questions info"
msgstr "<strong>Newest</strong> questions are shown first."
-#: templates/questions.html:135
+#: templates/questions.html:205
msgid "Questions are sorted by the <strong>time of last update</strong>."
msgstr ""
-#: templates/questions.html:136
+#: templates/questions.html:206
msgid "Most recently answered ones are shown first."
msgstr "<strong>Most recently answered</strong> questions are shown first."
-#: templates/questions.html:140
+#: templates/questions.html:210
msgid "Questions sorted by <strong>number of responses</strong>."
msgstr "Questions sorted by the <strong>number of answers</strong>."
-#: templates/questions.html:141
+#: templates/questions.html:211
msgid "Most answered questions are shown first."
msgstr " "
-#: templates/questions.html:145
+#: templates/questions.html:215
msgid "Questions are sorted by the <strong>number of votes</strong>."
msgstr ""
-#: templates/questions.html:146
+#: templates/questions.html:216
msgid "Most voted questions are shown first."
msgstr ""
-#: templates/questions.html:153 templates/unanswered.html:102
+#: templates/questions.html:224 templates/unanswered.html:118
msgid "Related tags"
msgstr "Tags"
+#: templates/questions.html:227 templates/tag_selector.html:10
+#: templates/tag_selector.html.py:27
+#, python-format
+msgid "see questions tagged '%(tag_name)s'"
+msgstr ""
+
#: templates/reopen.html:6 templates/reopen.html.py:16
msgid "Reopen question"
msgstr ""
@@ -1958,11 +2650,41 @@ msgstr ""
msgid "Reopen this question"
msgstr ""
-#: templates/revisions_answer.html:7 templates/revisions_answer.html.py:36
-#: templates/revisions_question.html:8 templates/revisions_question.html:36
+#: templates/revisions_answer.html:7 templates/revisions_answer.html.py:38
+#: templates/revisions_question.html:8 templates/revisions_question.html:38
msgid "Revision history"
msgstr ""
+#: templates/revisions_answer.html:50 templates/revisions_question.html:50
+msgid "click to hide/show revision"
+msgstr ""
+
+#: templates/tag_selector.html:4
+msgid "Interesting tags"
+msgstr ""
+
+#: templates/tag_selector.html:14
+#, python-format
+msgid "remove '%(tag_name)s' from the list of interesting tags"
+msgstr ""
+
+#: templates/tag_selector.html:20 templates/tag_selector.html.py:37
+msgid "Add"
+msgstr ""
+
+#: templates/tag_selector.html:21
+msgid "Ignored tags"
+msgstr ""
+
+#: templates/tag_selector.html:31
+#, python-format
+msgid "remove '%(tag_name)s' from the list of ignored tags"
+msgstr ""
+
+#: templates/tag_selector.html:40
+msgid "keep ingored questions hidden"
+msgstr ""
+
#: templates/tags.html:6 templates/tags.html.py:30
msgid "Tag list"
msgstr ""
@@ -1995,16 +2717,12 @@ msgstr ""
msgid "Nothing found"
msgstr ""
-#: templates/unanswered.html:7 templates/unanswered.html.py:18
-msgid "Unanswered questions"
-msgstr ""
-
-#: templates/unanswered.html:97
+#: templates/unanswered.html:114
#, python-format
msgid "have %(num_q)s unanswered questions"
msgstr ""
-"<div class=\"questions-count\">%(num_q)s</div><strong>unanswered</strong> "
-"questions"
+"<div class=\"questions-count\">%(num_q)s</div>questions <strong>without "
+"accepted answers</strong>"
#: templates/user_edit.html:6
msgid "Edit user profile"
@@ -2019,139 +2737,203 @@ msgid "image associated with your email address"
msgstr ""
#: templates/user_edit.html:31
-msgid "avatar"
-msgstr "<a href='/faq#gravatar'>gravatar</a>"
+#, python-format
+msgid "avatar, see %(gravatar_faq_url)s"
+msgstr "<a href='%(gravatar_faq_url)s'>gravatar</a>"
-#: templates/user_edit.html:36 templates/user_info.html:31
+#: templates/user_edit.html:36 templates/user_info.html:56
msgid "Registered user"
msgstr ""
-#: templates/user_edit.html:82
+#: templates/user_edit.html:86 templates/user_email_subscriptions.html:20
msgid "Update"
msgstr ""
-#: templates/user_info.html:34
+#: templates/user_email_subscriptions.html:8
+msgid "Email subscription settings"
+msgstr ""
+
+#: templates/user_email_subscriptions.html:9
+msgid "email subscription settings info"
+msgstr ""
+"<span class='big strong'>Adjust frequency of email updates.</span> Receive "
+"updates on interesting questions by email, <strong><br/>help the community</"
+"strong> by answering questions of your colleagues. If you do not wish to "
+"receive emails - select 'no email' on all items below.<br/>Updates are only "
+"sent when there is any new activity on selected items."
+
+#: templates/user_email_subscriptions.html:21
+msgid "Stop sending email"
+msgstr "Stop Email"
+
+#: templates/user_info.html:32
+msgid "Moderate this user"
+msgstr ""
+
+#: templates/user_info.html:45
msgid "update profile"
msgstr ""
-#: templates/user_info.html:40
+#: templates/user_info.html:60
msgid "real name"
msgstr ""
-#: templates/user_info.html:45
+#: templates/user_info.html:65
msgid "member for"
-msgstr ""
+msgstr "member since"
-#: templates/user_info.html:50
+#: templates/user_info.html:70
msgid "last seen"
msgstr ""
-#: templates/user_info.html:56
+#: templates/user_info.html:76
msgid "user website"
msgstr ""
-#: templates/user_info.html:62
+#: templates/user_info.html:82
msgid "location"
msgstr ""
-#: templates/user_info.html:69
+#: templates/user_info.html:89
msgid "age"
msgstr ""
-#: templates/user_info.html:70
+#: templates/user_info.html:90
msgid "age unit"
msgstr "years old"
-#: templates/user_info.html:76
+#: templates/user_info.html:96
msgid "todays unused votes"
msgstr ""
-#: templates/user_info.html:77
+#: templates/user_info.html:97
msgid "votes left"
msgstr ""
-#: templates/user_preferences.html:10
-msgid "Connect with Twitter"
-msgstr ""
-
-#: templates/user_preferences.html:13
-msgid "Twitter account name:"
-msgstr ""
-
-#: templates/user_preferences.html:15
-msgid "Twitter password:"
-msgstr ""
-
-#: templates/user_preferences.html:17
-msgid "Send my Questions to Twitter"
-msgstr ""
-
-#: templates/user_preferences.html:18
-msgid "Send my Answers to Twitter"
-msgstr ""
-
-#: templates/user_preferences.html:19
-msgid "Save"
-msgstr ""
+#: templates/user_stats.html:12
+#, python-format
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Question\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(counter)s</span> Questions\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/user_stats.html:10
-msgid "User questions"
-msgstr ""
+#: templates/user_stats.html:23
+#, python-format
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Answer\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(counter)s</span> Answers\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/user_stats.html:20
+#: templates/user_stats.html:36
#, python-format
msgid "the answer has been voted for %(vote_count)s times"
msgstr ""
-#: templates/user_stats.html:20
+#: templates/user_stats.html:36
msgid "this answer has been selected as correct"
msgstr ""
-#: templates/user_stats.html:28
+#: templates/user_stats.html:46
#, python-format
-msgid "the answer has been commented %(comment_count)s times"
-msgstr ""
+msgid ""
+"\n"
+" (one comment)\n"
+" "
+msgid_plural ""
+"\n"
+" the answer has been commented %(comment_count)s times\n"
+" "
+msgstr[0] ""
+"\n"
+"(one comment)"
+msgstr[1] ""
+"\n"
+"(%(comment_count)s comments)"
-#: templates/user_stats.html:36
-msgid "Votes"
-msgstr ""
+#: templates/user_stats.html:61
+#, python-format
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Vote\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(cnt)s</span> Votes\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/user_stats.html:41
+#: templates/user_stats.html:72
msgid "thumb up"
msgstr ""
-#: templates/user_stats.html:42
+#: templates/user_stats.html:73
msgid "user has voted up this many times"
msgstr ""
-#: templates/user_stats.html:46
+#: templates/user_stats.html:77
msgid "thumb down"
msgstr ""
-#: templates/user_stats.html:47
+#: templates/user_stats.html:78
msgid "user voted down this many times"
msgstr ""
-#: templates/user_stats.html:54
-msgid "Tags"
-msgstr ""
+#: templates/user_stats.html:87
+#, python-format
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Tag\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(counter)s</span> Tags\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/user_stats.html:61
+#: templates/user_stats.html:100
#, python-format
-msgid "see other questions tagged '%(tag)s' "
+msgid ""
+"see other questions with %(view_user)s's contributions tagged '%(tag_name)s' "
msgstr ""
+#: templates/user_stats.html:115
+#, python-format
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Badge\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(counter)s</span> Badges\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
#: templates/user_tabs.html:7
msgid "User profile"
msgstr ""
#: templates/user_tabs.html:16
msgid "graph of user reputation"
-msgstr ""
+msgstr "Graph of user karma"
#: templates/user_tabs.html:17
msgid "reputation history"
-msgstr ""
+msgstr "karma history"
#: templates/user_tabs.html:23
msgid "questions that user selected as his/her favorite"
@@ -2161,10 +2943,6 @@ msgstr ""
msgid "favorites"
msgstr ""
-#: templates/user_tabs.html:28
-msgid "settings"
-msgstr ""
-
#: templates/users.html:6 templates/users.html.py:24
msgid "Users"
msgstr ""
@@ -2202,16 +2980,21 @@ msgstr ""
msgid "thumb-up off"
msgstr ""
-#: templates/users_questions.html:35
+#: templates/users_questions.html:34
msgid "this answer has been accepted to be correct"
msgstr ""
-#: templates/authopenid/changeemail.html:7
-#: templates/authopenid/changeemail.html:33
+#: templates/authopenid/changeemail.html:3
+#: templates/authopenid/changeemail.html:9
+#: templates/authopenid/changeemail.html:38
msgid "Change email"
msgstr "Change Email"
-#: templates/authopenid/changeemail.html:10
+#: templates/authopenid/changeemail.html:11
+msgid "Save your email address"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:16
#, python-format
msgid "change %(email)s info"
msgstr ""
@@ -2219,57 +3002,65 @@ msgstr ""
"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: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:"
+#: templates/authopenid/changeemail.html:18
+#, python-format
+msgid "here is why email is required, see %(gravatar_faq_url)s"
msgstr ""
+"<span class='strong big'>Please enter your email address in the box below.</"
+"span> Valid email address is required on this Q&amp;A forum. If you like, "
+"you can <strong>receive updates</strong> on interesting questions or entire "
+"forum via email. Also, your email is used to create a unique <a href='%"
+"(gravatar_faq_url)s'><strong>gravatar</strong></a> image for your account. "
+"Email addresses are never shown or otherwise shared with anybody else."
-#: templates/authopenid/changeemail.html:30
+#: templates/authopenid/changeemail.html:31
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:31
-#: templates/authopenid/signin.html:154
-msgid "Password"
+msgid "Your Email"
+msgstr ""
+"<strong>Your Email</strong> (<i>must be valid, never shown to others</i>)"
+
+#: templates/authopenid/changeemail.html:38
+msgid "Save Email"
msgstr ""
-#: templates/authopenid/changeemail.html:42
+#: templates/authopenid/changeemail.html:49
msgid "Validate email"
msgstr ""
-#: templates/authopenid/changeemail.html:45
+#: templates/authopenid/changeemail.html:52
#, python-format
-msgid "validate %(email)s info"
+msgid "validate %(email)s info or go to %(change_email_url)s"
msgstr ""
"<span class=\"strong big\">An email with a validation link has been sent to %"
"(email)s.</span> Please <strong>follow the emailed link</strong> with your "
"web browser. Email validation is necessary to help insure the proper use of "
-"email on <span class=\"orange\">Q&amp;A</span> forum. If you would like "
-"to use <strong>another email</strong>, please <a href=\"/email/change/"
-"\"><strong>change it again</strong></a>."
+"email on <span class=\"orange\">Q&amp;A</span>. If you would like to use "
+"<strong>another email</strong>, please <a href='%(change_email_url)"
+"s'><strong>change it again</strong></a>."
-#: templates/authopenid/changeemail.html:50
+#: templates/authopenid/changeemail.html:57
msgid "Email not changed"
msgstr ""
-#: templates/authopenid/changeemail.html:53
+#: templates/authopenid/changeemail.html:60
#, python-format
-msgid "old %(email)s kept"
+msgid "old %(email)s kept, if you like go to %(change_email_url)s"
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."
+"it in your user profile or by using the <a href='%(change_email_url)"
+"s'><strong>previous form</strong></a> again."
-#: templates/authopenid/changeemail.html:58
+#: templates/authopenid/changeemail.html:65
msgid "Email changed"
msgstr ""
-#: templates/authopenid/changeemail.html:61
+#: templates/authopenid/changeemail.html:68
#, python-format
msgid "your current %(email)s can be used for this"
msgstr ""
@@ -2278,11 +3069,11 @@ msgstr ""
"Email notifications are sent once a day or less frequently - only when there "
"are any news."
-#: templates/authopenid/changeemail.html:66
+#: templates/authopenid/changeemail.html:73
msgid "Email verified"
msgstr ""
-#: templates/authopenid/changeemail.html:69
+#: templates/authopenid/changeemail.html:76
msgid "thanks for verifying email"
msgstr ""
"<span class=\"big strong\">Thank you for verifying your email!</span> Now "
@@ -2291,11 +3082,11 @@ msgstr ""
"updates</strong> - then will be notified about changes <strong>once a day</"
"strong> or less frequently."
-#: templates/authopenid/changeemail.html:74
+#: templates/authopenid/changeemail.html:81
msgid "email key not sent"
msgstr "Validation email not sent"
-#: templates/authopenid/changeemail.html:77
+#: templates/authopenid/changeemail.html:84
#, python-format
msgid "email key not sent %(email)s change email here %(change_link)s"
msgstr ""
@@ -2303,6 +3094,12 @@ msgstr ""
"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:4
+#: templates/authopenid/changeopenid.html:30
+#: templates/authopenid/settings.html:34
+msgid "Change OpenID"
+msgstr ""
+
#: templates/authopenid/changeopenid.html:8
msgid "Account: change OpenID URL"
msgstr ""
@@ -2312,97 +3109,148 @@ msgid ""
"This is where you can change your OpenID URL. Make sure you remember it!"
msgstr ""
+#: templates/authopenid/changeopenid.html:14
+#: templates/authopenid/delete.html:14 templates/authopenid/delete.html:24
+msgid "Please correct errors below:"
+msgstr ""
+
#: templates/authopenid/changeopenid.html:29
msgid "OpenID URL:"
msgstr ""
-#: templates/authopenid/changeopenid.html:30
-msgid "Change OpenID"
+#: templates/authopenid/changepw.html:5 templates/authopenid/changepw.html:14
+#: templates/authopenid/settings.html:29
+msgid "Change password"
msgstr ""
-#: templates/authopenid/changepw.html:14
+#: templates/authopenid/changepw.html:7
msgid "Account: change password"
-msgstr ""
+msgstr "Change your password"
-#: templates/authopenid/changepw.html:17
+#: templates/authopenid/changepw.html:8
msgid "This is where you can change your password. Make sure you remember it!"
msgstr ""
+"<span class='strong'>To change your password</span> please fill out and "
+"submit this form"
-#: templates/authopenid/changepw.html:27
-msgid "Current password"
-msgstr ""
-
-#: templates/authopenid/changepw.html:28
-msgid "New password"
-msgstr ""
-
-#: templates/authopenid/changepw.html:29
-msgid "New password again"
-msgstr ""
-
-#: templates/authopenid/changepw.html:30 templates/authopenid/settings.html:29
-msgid "Change password"
-msgstr ""
-
-#: templates/authopenid/complete.html:5
+#: templates/authopenid/complete.html:19
msgid "Connect your OpenID with this site"
msgstr "New user signup"
-#: templates/authopenid/complete.html:8
+#: templates/authopenid/complete.html:22
msgid "Connect your OpenID with your account on this site"
msgstr "New user signup"
-#: templates/authopenid/complete.html:12
+#: templates/authopenid/complete.html:27
#, python-format
-msgid "register new %(provider)s account info"
+msgid "register new %(provider)s account info, see %(gravatar_faq_url)s"
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>."
+"<a href='%(gravatar_faq_url)s'><strong>gravatar</strong></a>.</p>"
-#: templates/authopenid/complete.html:14
+#: templates/authopenid/complete.html:31
+#, python-format
+msgid ""
+"%(username)s already exists, choose another name for \n"
+" %(provider)s. Email is required too, see %"
+"(gravatar_faq_url)s\n"
+" "
+msgstr ""
+"<p><span class='strong big'>Oops... looks like screen name %(username)s is "
+"already used in another account.</span></p><p>Please choose another screen "
+"name to use with your %(provider)s login. Also, a valid email address is "
+"required on the <span class='orange'>Q&amp;A</span> forum. Your email is "
+"used to create a unique <a href='%(gravatar_faq_url)s'><strong>gravatar</"
+"strong></a> image for your account. If you like, you can <strong>receive "
+"updates</strong> on the interesting questions or entire forum by email. "
+"Email addresses are never shown or otherwise shared with anybody else.</p>"
+
+#: templates/authopenid/complete.html:35
+#, python-format
+msgid ""
+"register new external %(provider)s account info, see %(gravatar_faq_url)s"
+msgstr ""
+"<p><span class=\"big strong\">You are here for the first time with your %"
+"(provider)s login.</span></p><p>You can either keep your <strong>screen "
+"name</strong> the same as your %(provider)s login name or choose some other "
+"nickname.</p><p>Also, please save a valid <strong>email</strong> address. "
+"With the email you can <strong>subscribe for the updates</strong> on the "
+"most interesting questions. Email address is also used to create and "
+"retrieve your unique avatar image - <a href='%(gravatar_faq_url)"
+"s'><strong>gravatar</strong></a>.</p>"
+
+#: templates/authopenid/complete.html:40
msgid "This account already exists, please use another."
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='%(gravatar_faq_url)s'><strong>gravatar</strong></a>.</p>"
+
-#: templates/authopenid/complete.html:19 templates/authopenid/complete.html:32
-#: templates/authopenid/signin.html:138
+#: templates/authopenid/complete.html:55
msgid "Sorry, looks like we have some errors:"
msgstr ""
-#: templates/authopenid/complete.html:47
+#: templates/authopenid/complete.html:76
msgid "Screen name label"
msgstr "<strong>Screen Name</strong> (<i>will be shown to others</i>)"
-#: templates/authopenid/complete.html:48
+#: templates/authopenid/complete.html:83
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
+#: templates/authopenid/complete.html:89 templates/authopenid/signup.html:15
+msgid "receive updates motivational blurb"
+msgstr ""
+"<strong>Receive forum updates by email</strong> - this will help our "
+"community grow and become more useful.<br/>By default <span "
+"class='orange'>Q&amp;A</span> forum sends up to <strong>one email digest per "
+"week</strong> - only when there is anything new.<br/>If you like, please "
+"adjust this now or any time later from your user account."
+
+#: templates/authopenid/complete.html:91
+msgid "Tag filter tool will be your right panel, once you log in."
+msgstr ""
+"<strong>Receive forum updates by email</strong> - this will help our "
+"community grow and become more useful.<br/>By default "
+"<span class='orange'>Q&amp;A</span> forum sends up to <strong>one "
+"email digest per week</strong> - only when there is anything new.<br/>If "
+"you like, please adjust this now or any time later from your user account."
+
+#: templates/authopenid/complete.html:91
+msgid "create account"
+msgstr "Signup"
+
+#: templates/authopenid/complete.html:92
msgid "create account"
msgstr "Signup"
-#: templates/authopenid/complete.html:56
+#: templates/authopenid/complete.html:101
msgid "Existing account"
msgstr ""
-#: templates/authopenid/complete.html:57
+#: templates/authopenid/complete.html:102
msgid "user name"
msgstr ""
-#: templates/authopenid/complete.html:58 templates/authopenid/signin.html:128
+#: templates/authopenid/complete.html:103
msgid "password"
msgstr ""
-#: templates/authopenid/complete.html:61
+#: templates/authopenid/complete.html:108
msgid "Register"
msgstr ""
-#: templates/authopenid/complete.html:62 templates/authopenid/signin.html:156
+#: templates/authopenid/complete.html:109 templates/authopenid/signin.html:140
msgid "Forgot your password?"
msgstr ""
@@ -2415,12 +3263,11 @@ 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
+#: templates/authopenid/delete.html:19
msgid "Password:"
msgstr ""
@@ -2430,45 +3277,51 @@ msgstr ""
#: templates/authopenid/confirm_email.txt:12
#: templates/authopenid/email_validation.txt:14
-#: templates/authopenid/sendpw_email.txt:13
+#: templates/authopenid/sendpw_email.txt:8
msgid ""
"Sincerely,\n"
"Forum Administrator"
msgstr ""
+"Sincerely,\n"
+"Q&A Forum Administrator"
+
+#: templates/authopenid/delete.html:4 templates/authopenid/settings.html:38
+msgid "Delete account"
+msgstr ""
-#: templates/authopenid/delete.html:9
+#: templates/authopenid/delete.html:8
msgid "Account: delete account"
msgstr ""
-#: templates/authopenid/delete.html:13
+#: templates/authopenid/delete.html:12
msgid ""
"Note: After deleting your account, anyone will be able to register this "
"username."
msgstr ""
-#: templates/authopenid/delete.html:17
+#: templates/authopenid/delete.html:16
msgid "Check confirm box, if you want delete your account."
msgstr ""
-#: templates/authopenid/delete.html:32
+#: templates/authopenid/delete.html:31
msgid "I am sure I want to delete my account."
msgstr ""
-#: templates/authopenid/delete.html:33
+#: templates/authopenid/delete.html:32
msgid "Password/OpenID URL"
msgstr ""
-#: templates/authopenid/delete.html:33
+#: templates/authopenid/delete.html:32
msgid "(required for your security)"
msgstr ""
-#: templates/authopenid/delete.html:35
+#: templates/authopenid/delete.html:34
msgid "Delete account permanently"
msgstr ""
#: templates/authopenid/email_validation.txt:2
msgid "Greetings from the Q&A forum"
-msgstr "Greetings from the Q&amp;A forum"
+msgstr ""
#: templates/authopenid/email_validation.txt:4
msgid "To make use of the Forum, please follow the link below:"
@@ -2485,38 +3338,47 @@ msgid ""
"for any inconvenience"
msgstr ""
-#: templates/authopenid/sendpw.html:4 templates/authopenid/sendpw.html.py:8
-msgid "Send new password"
+#: templates/authopenid/external_legacy_login_info.html:4
+#: templates/authopenid/external_legacy_login_info.html:7
+msgid "Traditional login information"
msgstr ""
+"<span class='big strong'>Forgot you password? No problems - just get a new "
+"one!</span><br/>Please follow the following steps:<br/>&bull; submit your "
+"user name below and check your email<br/>&bull; <strong>follow the "
+"activation link</strong> for the new password - sent to you by email and "
+"login with the suggested password<br/>&bull; at this you might want to "
+"change your password to something you can remember better"
-#: templates/authopenid/sendpw.html:12
-msgid "Lost your password? No problem - here you can reset it."
-msgstr ""
+#: templates/authopenid/sendpw.html:21
+msgid "Reset password"
+msgstr "Send me a new password"
-#: templates/authopenid/sendpw.html:13
-msgid ""
-"Please enter your username below and new password will be sent to your "
-"registered e-mail"
+#: templates/authopenid/external_legacy_login_info.html:12
+msgid "how to login with password through external login website"
msgstr ""
-#: templates/authopenid/sendpw.html:28
-msgid "User name"
+#: templates/authopenid/sendpw.html:4 templates/authopenid/sendpw.html.py:7
+msgid "Send new password"
+msgstr "Recover password"
+
+#: templates/authopenid/sendpw.html:10
+msgid "password recovery information"
msgstr ""
+"<span class='big strong'>Forgot you password? No problems - just get a new "
+"one!</span><br/>Please follow the following steps:<br/>&bull; submit your "
+"user name below and check your email<br/>&bull; <strong>follow the "
+"activation link</strong> for the new password - sent to you by email and "
+"login with the suggested password<br/>&bull; at this you might want to "
+"change your password to something you can remember better"
-#: templates/authopenid/sendpw.html:30
+#: templates/authopenid/sendpw.html:21
msgid "Reset password"
-msgstr ""
+msgstr "Send me a new password"
-#: templates/authopenid/sendpw.html:30
+#: templates/authopenid/sendpw.html:22
msgid "return to login"
msgstr ""
-#: 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/sendpw_email.txt:2
#, python-format
msgid ""
@@ -2525,15 +3387,18 @@ msgid ""
msgstr ""
#: templates/authopenid/sendpw_email.txt:5
-msgid "Your new account details are:"
-msgstr ""
-
-#: templates/authopenid/sendpw_email.txt:8
-msgid "New password:"
+#, python-format
+msgid ""
+"email explanation how to use new %(password)s for %(username)s\n"
+"with the %(key_link)s"
msgstr ""
+"To change your password, please follow these steps:\n"
+"* visit this link: %(key_link)s\n"
+"* login with user name %(username)s and password %(password)s\n"
+"* go to your user profile and set the password to something you can remember"
-#: templates/authopenid/sendpw_email.txt:10
-msgid "To confirm that you wanted to reset your password please visit:"
+#: templates/authopenid/settings.html:4
+msgid "Account functions"
msgstr ""
#: templates/authopenid/settings.html:30
@@ -2552,15 +3417,11 @@ msgstr ""
msgid "Change openid associated to your account"
msgstr ""
-#: templates/authopenid/settings.html:38
-msgid "Delete account"
-msgstr ""
-
#: templates/authopenid/settings.html:39
msgid "Erase your username and all your data from website"
msgstr ""
-#: templates/authopenid/signin.html:4 templates/authopenid/signin.html:21
+#: templates/authopenid/signin.html:5 templates/authopenid/signin.html:21
msgid "User login"
msgstr "User login"
@@ -2588,148 +3449,120 @@ msgstr ""
"strong> %(summary)s...\"</i> <span class=\"strong big\">is saved and will be "
"posted once you log in.</span>"
-#: templates/authopenid/signin.html:40
+#: templates/authopenid/signin.html:42
msgid "Click to sign in through any of these services."
msgstr ""
-"<p><span class=\"big strong\">Please select your favorite login method below.</span>"
-"</p><p><font color=\"gray\">External login services use <a "
-"href=\"http://openid.net\"><b>OpenID</b></a> technology that increases "
-"security of your online identity and makes login process simpler. First option "
-"requires login name and password.</font></p>"
+"<p><span class=\"big strong\">Please select your favorite login method below."
+"</span></p><p><font color=\"gray\">External login services use <a href="
+"\"http://openid.net\"><b>OpenID</b></a> technology, where your password "
+"always stays confidential between you and your login provider and you don't "
+"have to remember another one. CNPROG option requires your login name and "
+"password entered here.</font></p>"
-#: templates/authopenid/signin.html:113
+#: templates/authopenid/signin.html:117
msgid "Enter your <span id=\"enter_your_what\">Provider user name</span>"
msgstr ""
-"<span class=\"big strong\">Enter your <span id=\"enter_your_what\">Provider "
-"user name</span></span><br><span class='grey'>(or select another login method above)</span>"
+"<span class=\"big strong\">Enter your </span><span id=\"enter_your_what\" "
+"class='big strong'>Provider user name</span><br/><span class='grey'>(or "
+"select another login method above)</span>"
-#: templates/authopenid/signin.html:120
+#: templates/authopenid/signin.html:124
msgid ""
"Enter your <a class=\"openid_logo\" href=\"http://openid.net\">OpenID</a> "
"web address"
msgstr ""
"<span class=\"big strong\">Enter your <a class=\"openid_logo\" href=\"http://"
-"openid.net\">OpenID</a> web address</span><br><span class='grey'>(or choose another login "
-"method above)</span>"
+"openid.net\">OpenID</a> web address</span><br/><span class='grey'>(or choose "
+"another login method above)</span>"
-#: templates/authopenid/signin.html:122 templates/authopenid/signin.html:130
-#: templates/authopenid/signin.html:155
+#: templates/authopenid/signin.html:126 templates/authopenid/signin.html:138
msgid "Login"
msgstr ""
-#: templates/authopenid/signin.html:125
+#: templates/authopenid/signin.html:129
msgid "Enter your login name and password"
msgstr ""
-"<span class='big strong'>Enter your forum login and password</span><br/>"
-"<span class='grey'>(or select your OpenID provider above)</span>"
-
-#: templates/authopenid/signin.html:126
-msgid "login name"
-msgstr ""
+"<span class='big strong'>Enter your CNPROG login and password</span><br/"
+"><span class='grey'>(or select your OpenID provider above)</span>"
-#: templates/authopenid/signin.html:134
-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:152
-msgid "Use login name and password"
+#: templates/authopenid/signin.html:133
+msgid "Login name"
msgstr ""
-#: templates/authopenid/signin.html:153
-msgid "Login name"
+#: templates/authopenid/signin.html:135
+msgid "Password"
msgstr ""
-#: templates/authopenid/signin.html:157
-msgid "Create new account"
+#: templates/authopenid/signin.html:139
+msgid "Create account"
msgstr ""
-#: templates/authopenid/signin.html:166
+#: templates/authopenid/signin.html:149
msgid "Why use OpenID?"
msgstr ""
-#: templates/authopenid/signin.html:169
+#: templates/authopenid/signin.html:152
msgid "with openid it is easier"
msgstr "With the OpenID you don't need to create new username and password."
-#: templates/authopenid/signin.html:172
+#: templates/authopenid/signin.html:155
msgid "reuse openid"
msgstr "You can safely re-use the same login for all OpenID-enabled websites."
-#: templates/authopenid/signin.html:175
+#: templates/authopenid/signin.html:158
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:178
+#: templates/authopenid/signin.html:161
msgid "openid is supported open standard"
msgstr "OpenID is based on an open standard, supported by many organizations."
-#: templates/authopenid/signin.html:183
+#: templates/authopenid/signin.html:166
msgid "Find out more"
msgstr ""
-#: templates/authopenid/signin.html:184
+#: templates/authopenid/signin.html:167
msgid "Get OpenID"
msgstr ""
-#: templates/authopenid/signup.html:4 templates/authopenid/signup.html.py:8
+#: templates/authopenid/signup.html:4
msgid "Signup"
msgstr ""
-#: templates/authopenid/signup.html:12
-msgid ""
-"We support two types of user registration: conventional username/password, "
-"and"
+#: templates/authopenid/signup.html:8
+msgid "Create login name and password"
msgstr ""
-#: templates/authopenid/signup.html:12
-msgid "the OpenID method"
+#: templates/authopenid/signup.html:10
+msgid "Traditional signup info"
msgstr ""
+"<span class='strong big'>If you prefer, create your forum login name and "
+"password here. However</span>, please keep in mind that we also support "
+"<strong>OpenID</strong> login method. With <strong>OpenID</strong> you can "
+"simply reuse your external login (e.g. Gmail or AOL) without ever sharing "
+"your login details with anyone and having to remember yet another password."
#: templates/authopenid/signup.html:17
-msgid "Sorry, looks like we have some errors"
-msgstr ""
-
-#: templates/authopenid/signup.html:35
-msgid "Conventional registration"
-msgstr ""
-
-#: templates/authopenid/signup.html:36
-msgid "choose a user name"
-msgstr ""
-
-#: templates/authopenid/signup.html:42
-msgid "back to login"
-msgstr ""
-
-#: templates/authopenid/signup.html:46
-msgid "Register with your OpenID"
+msgid "Create Account"
msgstr ""
-#: templates/authopenid/signup.html:49
-msgid "Login with your OpenID"
+#: templates/authopenid/signup.html:19
+msgid "return to OpenID login"
msgstr ""
-#
-#~ 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."
+#~ "\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>"
diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo
index d25949ed..2b514069 100644
--- a/locale/es/LC_MESSAGES/django.mo
+++ b/locale/es/LC_MESSAGES/django.mo
Binary files differ
diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po
index fe73330c..83ff69bf 100644
--- a/locale/es/LC_MESSAGES/django.po
+++ b/locale/es/LC_MESSAGES/django.po
@@ -1,1216 +1,1453 @@
+# 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: \n"
+"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-08-21 17:28+0000\n"
-"PO-Revision-Date: 2009-08-21 11:29-0600\n"
-"Last-Translator: Bruno Sarlo <bsarlo@gmail.com>\n"
+"POT-Creation-Date: 2010-02-09 20:10+0000\n"
+"PO-Revision-Date: 2010-02-09 14:11-0600\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: settings.py:12 urls.py:25 forum/views.py:310 forum/views.py:705
-msgid "account/"
-msgstr "cuenta/"
-
-#: settings.py:12 urls.py:26 django_authopenid/urls.py:9
-#: django_authopenid/urls.py:10 django_authopenid/urls.py:11
-#: django_authopenid/urls.py:13 forum/views.py:310 forum/views.py:706
-msgid "signin/"
-msgstr "ingresar/"
-
-#: urls.py:22
-msgid "upfiles/"
-msgstr "archivossubidos/"
-
-#: urls.py:27 django_authopenid/urls.py:16
-msgid "signup/"
-msgstr "registrarse/"
-
-#: urls.py:28 urls.py:29 urls.py:30 django_authopenid/urls.py:24
-#: django_authopenid/urls.py:25
-msgid "email/"
-msgstr "email/"
-
-#: urls.py:28
-msgid "change/"
-msgstr "cambiar/"
-
-#: urls.py:29
-msgid "sendkey/"
-msgstr "enviarclave/"
-
-#: urls.py:30
-msgid "verify/"
-msgstr "verificar/"
-
-#: urls.py:31
-msgid "about/"
-msgstr "acercadenosotros/"
-
-#: urls.py:32
-msgid "faq/"
-msgstr "preguntasfrecuentes/"
-
-#: urls.py:33
-msgid "privacy/"
-msgstr "códigodeprivacidad/"
-
-#: urls.py:34
-msgid "logout/"
-msgstr "cerrarsesion/"
-
-#: urls.py:35 urls.py:36 urls.py:37 urls.py:49 forum/models.py:425
-msgid "answers/"
-msgstr "respuestas/"
-
-#: urls.py:35 urls.py:47
-msgid "comments/"
-msgstr "comentarios/"
-
-#: urls.py:36 urls.py:41 urls.py:55 templates/user_info.html:34
-msgid "edit/"
-msgstr "editar/"
-
-#: urls.py:37 urls.py:46
-msgid "revisions/"
-msgstr "revisiones/"
-
-#: urls.py:38 urls.py:39 urls.py:40 urls.py:41 urls.py:42 urls.py:43
-#: urls.py:44 urls.py:45 urls.py:46 urls.py:47 urls.py:48 forum/feed.py:19
-#: forum/models.py:313 forum/views.py:1228 forum/views.py:1230
-#: forum/views.py:1470
-msgid "questions/"
-msgstr "preguntas/"
-
-#: urls.py:39 urls.py:65
-msgid "ask/"
-msgstr "preguntar/"
+#: django_authopenid/forms.py:70
+msgid "choose a username"
+msgstr ""
-#: urls.py:40
-msgid "unanswered/"
-msgstr "sinrespuesta/"
+#: django_authopenid/forms.py:76
+msgid "user name is required"
+msgstr ""
-#: urls.py:42
-msgid "close/"
-msgstr "cerrar/"
+#: django_authopenid/forms.py:77
+msgid "sorry, this name is taken, please choose another"
+msgstr ""
-#: urls.py:43
-msgid "reopen/"
-msgstr "reabrir/"
+#: django_authopenid/forms.py:78
+msgid "sorry, this name is not allowed, please choose another"
+msgstr ""
-#: urls.py:44
-msgid "answer/"
-msgstr "respuesta/"
+#: django_authopenid/forms.py:79
+msgid "sorry, there is no user with this name"
+msgstr ""
-#: urls.py:45
-msgid "vote/"
-msgstr "votar/"
+#: django_authopenid/forms.py:80
+msgid "sorry, we have a serious error - user name is taken by several users"
+msgstr ""
-#: urls.py:48 urls.py:49 django_authopenid/urls.py:27
-msgid "delete/"
-msgstr "borrar/"
+#: django_authopenid/forms.py:81
+msgid "user name can only consist of letters, empty space and underscore"
+msgstr ""
-#: urls.py:51
-msgid "question/"
-msgstr "pregunta/"
+#: django_authopenid/forms.py:116
+msgid "your email address"
+msgstr ""
-#: urls.py:52 urls.py:53 forum/views.py:747 forum/views.py:2067
-msgid "tags/"
-msgstr "etiquetas/"
+#: django_authopenid/forms.py:117
+msgid "email address is required"
+msgstr ""
-#: urls.py:54 urls.py:55 urls.py:56 forum/views.py:1034 forum/views.py:1038
-#: forum/views.py:1472 forum/views.py:1805 forum/views.py:2069
-msgid "users/"
-msgstr "usuarios/"
+#: django_authopenid/forms.py:118
+msgid "please enter a valid email address"
+msgstr ""
-#: urls.py:57 urls.py:58
-msgid "badges/"
-msgstr "distinciones/"
+#: django_authopenid/forms.py:119
+msgid "this email is already used by someone else, please choose another"
+msgstr ""
-#: urls.py:59
-msgid "messages/"
-msgstr "mensajes/"
+#: django_authopenid/forms.py:163 django_authopenid/views.py:118
+msgid "i-names are not supported"
+msgstr ""
-#: urls.py:59
-msgid "markread/"
-msgstr "marcarleido/"
+#: django_authopenid/forms.py:219
+msgid "Account with this name already exists on the forum"
+msgstr ""
-#: urls.py:61
-msgid "nimda/"
-msgstr "administrador/"
+#: django_authopenid/forms.py:220
+msgid "can't have two logins to the same account yet, sorry."
+msgstr ""
-#: urls.py:63
-msgid "upload/"
-msgstr "subir/"
+#: django_authopenid/forms.py:242
+msgid "Please enter valid username and password (both are case-sensitive)."
+msgstr ""
-#: urls.py:64 urls.py:65 urls.py:66
-msgid "books/"
-msgstr "libros/"
+#: django_authopenid/forms.py:245 django_authopenid/forms.py:295
+msgid "This account is inactive."
+msgstr ""
-#: urls.py:67
-msgid "search/"
-msgstr "buscar/"
+#: django_authopenid/forms.py:247
+msgid "Login failed."
+msgstr ""
-#: 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:249
+msgid "Please enter username and password"
+msgstr ""
-#: django_authopenid/forms.py:102
-msgid ""
-"Usernames can only contain letters, numbers and "
-"underscores"
+#: django_authopenid/forms.py:251
+msgid "Please enter your password"
msgstr ""
-"Los nombres de usuario solo pueden contener letras, números y guión bajo"
-#: django_authopenid/forms.py:109
-msgid ""
-"This username does not exist in our database. Please "
-"choose another."
+#: django_authopenid/forms.py:253
+msgid "Please enter user name"
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:233
+#: django_authopenid/forms.py:291
msgid ""
"Please enter a valid username and password. Note that "
"both fields are case-sensitive."
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:237
-msgid "This account is inactive."
-msgstr "Esta cuenta esta inactiva."
-
-#: django_authopenid/forms.py:158 django_authopenid/forms.py:210
-msgid "invalid user name"
-msgstr "nombre de usuario no valido"
-#: django_authopenid/forms.py:160
-msgid "sorry, this name can not be used, please try another"
+#: django_authopenid/forms.py:313
+msgid "choose password"
msgstr ""
-"perdón, pero este nombre de usuario no puede ser usado, intente con otro"
-
-#: django_authopenid/forms.py:162
-msgid "username too short"
-msgstr "nombre de usuario muy corto"
-#: django_authopenid/forms.py:170 django_authopenid/forms.py:171
-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:314
+msgid "password is required"
+msgstr ""
-#: django_authopenid/forms.py:185
-msgid ""
-"This email is already registered in our database. "
-"Please choose another."
+#: django_authopenid/forms.py:317
+msgid "retype password"
msgstr ""
-"Este email ya está registrado en nuestra base de datos. Por favor, intente "
-"con otro."
-#: 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:318
+msgid "please, retype your password"
+msgstr ""
-#: django_authopenid/forms.py:255
-msgid "choose a username"
-msgstr "elija un nombre de usuario"
+#: django_authopenid/forms.py:319
+msgid "sorry, entered passwords did not match, please try again"
+msgstr ""
-#: django_authopenid/forms.py:257 templates/authopenid/signup.html:38
-msgid "your email address"
-msgstr "su email (correo electrónico)"
+#: django_authopenid/forms.py:344
+msgid "Current password"
+msgstr ""
-#: django_authopenid/forms.py:259 templates/authopenid/signup.html:39
-msgid "choose password"
-msgstr "elija una contraseña"
+#: django_authopenid/forms.py:346
+msgid "New password"
+msgstr ""
-#: django_authopenid/forms.py:261 templates/authopenid/signup.html:40
-msgid "retype password"
-msgstr "re-ingrese la contraseña"
+#: django_authopenid/forms.py:348
+msgid "Retype new password"
+msgstr ""
-#: django_authopenid/forms.py:335
+#: django_authopenid/forms.py:359
msgid ""
"Old password is incorrect. Please enter the correct "
"password."
-msgstr "La antigua contraseña es incorrecta. Por favor ingrese la correcta"
+msgstr ""
-#: django_authopenid/forms.py:347
+#: django_authopenid/forms.py:371
msgid "new passwords do not match"
-msgstr "la nueva contraseña no coincide"
+msgstr ""
-#: django_authopenid/forms.py:442
+#: django_authopenid/forms.py:435
+msgid "Your user name (<i>required</i>)"
+msgstr ""
+
+#: django_authopenid/forms.py:450
msgid "Incorrect username."
-msgstr "Nombre de usuario incorrecto"
+msgstr ""
-#: django_authopenid/urls.py:10 forum/views.py:310 forum/views.py:706
+#: django_authopenid/urls.py:9 django_authopenid/urls.py:10
+#: django_authopenid/urls.py:11 django_authopenid/urls.py:13 forum/urls.py:29
+msgid "signin/"
+msgstr ""
+
+#: django_authopenid/urls.py:10
msgid "newquestion/"
-msgstr "nuevapregunta/"
+msgstr ""
#: django_authopenid/urls.py:11
msgid "newanswer/"
-msgstr "respuesta-nueva/"
+msgstr ""
#: django_authopenid/urls.py:12
msgid "signout/"
-msgstr "salir/"
+msgstr ""
#: django_authopenid/urls.py:13
msgid "complete/"
-msgstr "completado/"
+msgstr ""
+
+#: django_authopenid/urls.py:15
+msgid "external-login/"
+msgstr ""
+
+#: django_authopenid/urls.py:16
+msgid "register/"
+msgstr ""
-#: django_authopenid/urls.py:18
+#: django_authopenid/urls.py:17
+msgid "signup/"
+msgstr ""
+
+#: django_authopenid/urls.py:19
msgid "sendpw/"
-msgstr "enviarcontrasena/"
+msgstr ""
-#: django_authopenid/urls.py:19 django_authopenid/urls.py:23
-#, fuzzy
+#: django_authopenid/urls.py:20 django_authopenid/urls.py:24
msgid "password/"
-msgstr "contraseña"
+msgstr ""
-#: django_authopenid/urls.py:19
+#: django_authopenid/urls.py:20
msgid "confirm/"
msgstr ""
-#: django_authopenid/urls.py:22
-#, fuzzy
+#: django_authopenid/urls.py:23
msgid "account_settings"
-msgstr "preferencias"
+msgstr ""
+
+#: django_authopenid/urls.py:25 django_authopenid/urls.py:26
+#: django_authopenid/urls.py:27 django_authopenid/urls.py:28
+msgid "email/"
+msgstr ""
#: django_authopenid/urls.py:25
msgid "validate/"
msgstr ""
-#: django_authopenid/views.py:108
+#: django_authopenid/urls.py:26
+msgid "change/"
+msgstr ""
+
+#: django_authopenid/urls.py:27
+msgid "sendkey/"
+msgstr ""
+
+#: django_authopenid/urls.py:28
+msgid "verify/"
+msgstr ""
+
+#: django_authopenid/urls.py:29
+msgid "openid/"
+msgstr ""
+
+#: django_authopenid/urls.py:30 forum/urls.py:49 forum/urls.py:53
+msgid "delete/"
+msgstr ""
+
+#: django_authopenid/views.py:124
#, python-format
msgid "OpenID %(openid_url)s is invalid"
-msgstr "El OpenID %(openid_url)s no es valido"
+msgstr ""
-#: django_authopenid/views.py:419 django_authopenid/views.py:546
-msgid "Welcome"
-msgstr "Bienvenido"
+#: django_authopenid/views.py:532
+msgid "Welcome email subject line"
+msgstr ""
-#: django_authopenid/views.py:509
+#: django_authopenid/views.py:627
msgid "Password changed."
-msgstr "Contraseña modificada"
+msgstr ""
-#: django_authopenid/views.py:521 django_authopenid/views.py:526
-msgid "your email needs to be validated"
-msgstr "su correo electrónico necesita ser validado"
+#: django_authopenid/views.py:639 django_authopenid/views.py:645
+#, python-format
+msgid "your email needs to be validated see %(details_url)s"
+msgstr ""
+
+#: django_authopenid/views.py:666
+msgid "Email verification subject line"
+msgstr ""
-#: django_authopenid/views.py:683 django_authopenid/views.py:835
+#: django_authopenid/views.py:752
+msgid "your email was not changed"
+msgstr ""
+
+#: django_authopenid/views.py:799 django_authopenid/views.py:951
#, python-format
msgid "No OpenID %s found associated in our database"
-msgstr "El OpenID %s no esta asociada en nuestra base de datos"
+msgstr ""
-#: django_authopenid/views.py:687 django_authopenid/views.py:842
+#: django_authopenid/views.py:803 django_authopenid/views.py:958
#, python-format
msgid "The OpenID %s isn't associated to current user logged in"
-msgstr "El OpenID %s no esta asociada al usuario actualmente autenticado"
+msgstr ""
-#: django_authopenid/views.py:695
+#: django_authopenid/views.py:811
msgid "Email Changed."
-msgstr "Email modificado"
+msgstr ""
-#: django_authopenid/views.py:770
+#: django_authopenid/views.py:886
msgid "This OpenID is already associated with another account."
-msgstr "Este OpenID ya está asociada a otra cuenta."
+msgstr ""
-#: django_authopenid/views.py:775
+#: django_authopenid/views.py:891
#, python-format
msgid "OpenID %s is now associated with your account."
-msgstr "El OpenID %s está ahora asociada con tu cuenta."
+msgstr ""
-#: django_authopenid/views.py:845
+#: django_authopenid/views.py:961
msgid "Account deleted."
-msgstr "Cuenta borrada."
+msgstr ""
-#: django_authopenid/views.py:885
+#: django_authopenid/views.py:1004
msgid "Request for new password"
-msgstr "Pedir nueva contraseña"
+msgstr ""
-#: django_authopenid/views.py:898
-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:1017
+msgid "A new password and the activation link were sent to your email address."
+msgstr ""
-#: django_authopenid/views.py:928
+#: django_authopenid/views.py:1047
#, python-format
msgid ""
"Could not change password. Confirmation key '%s' is not "
"registered."
msgstr ""
-"No se ha podido modificar la contraseña. La clave de confirmación '%s' no "
-"está registrada"
-#: django_authopenid/views.py:937
+#: django_authopenid/views.py:1056
msgid ""
"Can not change password. User don't exist anymore in our "
"database."
msgstr ""
-"No se puede cambiar la contraseña. El usuario no existe más en nuestra base "
-"de datos."
-#: django_authopenid/views.py:946
+#: django_authopenid/views.py:1065
#, python-format
msgid "Password changed for %s. You may now sign in."
-msgstr "Contraseña cambiada por %s. Ahora puedes ingresar."
+msgstr ""
+
+#: forum/auth.py:484
+msgid "Your question and all of it's answers have been deleted"
+msgstr ""
+
+#: forum/auth.py:486
+msgid "Your question has been deleted"
+msgstr ""
+
+#: forum/auth.py:489
+msgid "The question and all of it's answers have been deleted"
+msgstr ""
+
+#: forum/auth.py:491
+msgid "The question has been deleted"
+msgstr ""
#: forum/const.py:8
msgid "duplicate question"
-msgstr "pregunta duplicada"
+msgstr ""
#: forum/const.py:9
-msgid "question if off-topic or not relevant"
-msgstr "pregunta esta fuera de tema o no es relevante"
+msgid "question is off-topic or not relevant"
+msgstr ""
#: forum/const.py:10
msgid "too subjective and argumentative"
-msgstr "demasiado subjetiva o argumentativa"
+msgstr ""
#: forum/const.py:11
msgid "is not an answer to the question"
-msgstr "no es una respuesta a la pregunta"
+msgstr ""
#: forum/const.py:12
msgid "the question is answered, right answer was accepted"
-msgstr "la pregunta esta respondida, se ha aceptado la respuesta correcta"
+msgstr ""
#: forum/const.py:13
msgid "problem is not reproducible or outdated"
-msgstr "el problema no es reproducible o caducó"
+msgstr ""
#: forum/const.py:15
msgid "question contains offensive inappropriate, or malicious remarks"
-msgstr "la pregunta contiene frases ofensivas, inapropiadas o maliciosas."
+msgstr ""
#: forum/const.py:16
msgid "spam or advertising"
-msgstr "spam o publicidad"
+msgstr ""
-#: forum/const.py:56
+#: forum/const.py:57
msgid "question"
-msgstr "pregunta"
+msgstr ""
-#: forum/const.py:57 templates/book.html:110
+#: forum/const.py:58 templates/book.html:110
msgid "answer"
-msgstr "respuesta"
+msgstr ""
-#: forum/const.py:58
+#: forum/const.py:59
msgid "commented question"
-msgstr "pregunta comentada"
+msgstr ""
-#: forum/const.py:59
+#: forum/const.py:60
msgid "commented answer"
-msgstr "respuesta comentada"
+msgstr ""
-#: forum/const.py:60
+#: forum/const.py:61
msgid "edited question"
-msgstr "pregunta editada"
+msgstr ""
-#: forum/const.py:61
+#: forum/const.py:62
msgid "edited answer"
-msgstr "respuesta editada"
+msgstr ""
-#: forum/const.py:62
+#: forum/const.py:63
msgid "received award"
-msgstr "premio recibido"
+msgstr ""
-#: forum/const.py:63
+#: forum/const.py:64
msgid "marked best answer"
-msgstr "marcada como mejor respuesta"
+msgstr ""
-#: forum/const.py:64
+#: forum/const.py:65
msgid "upvoted"
-msgstr "votada positivo"
+msgstr ""
-#: forum/const.py:65
+#: forum/const.py:66
msgid "downvoted"
-msgstr "votada negativo"
+msgstr ""
-#: forum/const.py:66
+#: forum/const.py:67
msgid "canceled vote"
-msgstr "voto cancelado"
+msgstr ""
-#: forum/const.py:67
+#: forum/const.py:68
msgid "deleted question"
-msgstr "pregunta borrada"
+msgstr ""
-#: forum/const.py:68
+#: forum/const.py:69
msgid "deleted answer"
-msgstr "respuesta borrada"
+msgstr ""
-#: forum/const.py:69
+#: forum/const.py:70
msgid "marked offensive"
-msgstr "marcada como ofensiva"
+msgstr ""
-#: forum/const.py:70
+#: forum/const.py:71
msgid "updated tags"
-msgstr "etiquetas actualizadas"
+msgstr ""
-#: forum/const.py:71
+#: forum/const.py:72
msgid "selected favorite"
-msgstr "seleccionada como favorita"
+msgstr ""
-#: forum/const.py:72
+#: forum/const.py:73
msgid "completed user profile"
-msgstr "completó perfil de usuario"
+msgstr ""
+
+#: forum/const.py:74
+msgid "email update sent to user"
+msgstr ""
-#: forum/const.py:83
+#: forum/const.py:85
msgid "[closed]"
-msgstr "[cerrada]"
+msgstr ""
-#: forum/const.py:84
+#: forum/const.py:86
msgid "[deleted]"
-msgstr "[borrada]"
+msgstr ""
-#: forum/const.py:85
+#: forum/const.py:87 forum/views.py:777 forum/views.py:796
msgid "initial version"
-msgstr "versión inicial"
+msgstr ""
-#: forum/const.py:86
+#: forum/const.py:88
msgid "retagged"
-msgstr "re-etiquetada"
+msgstr ""
+
+#: forum/const.py:92
+msgid "exclude ignored tags"
+msgstr ""
+
+#: forum/const.py:92
+msgid "allow only selected tags"
+msgstr ""
#: forum/feed.py:18
msgid " - "
-msgstr " - "
+msgstr ""
#: forum/feed.py:18
msgid "latest questions"
-msgstr "últimas preguntas"
+msgstr ""
-#: forum/forms.py:14 templates/answer_edit_tips.html:33
-#: templates/answer_edit_tips.html.py:37 templates/question_edit_tips.html:31
-#: templates/question_edit_tips.html:36
+#: forum/feed.py:19 forum/urls.py:57
+msgid "question/"
+msgstr ""
+
+#: forum/forms.py:16 templates/answer_edit_tips.html:35
+#: templates/answer_edit_tips.html.py:39 templates/question_edit_tips.html:32
+#: templates/question_edit_tips.html:37
msgid "title"
-msgstr "título"
+msgstr ""
-#: forum/forms.py:15
+#: forum/forms.py:17
msgid "please enter a descriptive title for your question"
-msgstr "ingrese un título descriptivo para su pregunta"
+msgstr ""
-#: forum/forms.py:20
+#: forum/forms.py:22
msgid "title must be > 10 characters"
-msgstr "el título debe tener al menos 10 caracteres"
+msgstr ""
-#: forum/forms.py:29
+#: forum/forms.py:31
msgid "content"
-msgstr "contenido"
+msgstr ""
-#: forum/forms.py:35
+#: forum/forms.py:37
msgid "question content must be > 10 characters"
-msgstr "el contenido de la pregunta debe ser al menos de 10 caracteres"
+msgstr ""
-#: forum/forms.py:45 templates/header.html:30 templates/header.html.py:64
+#: forum/forms.py:47 templates/header.html:28 templates/header.html.py:62
msgid "tags"
-msgstr "etiquetas"
+msgstr ""
-#: forum/forms.py:47
+#: forum/forms.py:49
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:54 templates/question_retag.html:38
+#: forum/forms.py:56 templates/question_retag.html:39
msgid "tags are required"
-msgstr "las etiquetas son requeridas"
+msgstr ""
-#: forum/forms.py:58
+#: forum/forms.py:62
msgid "please use 5 tags or less"
-msgstr "por favor use 5 o menos etiquetas"
+msgstr ""
-#: forum/forms.py:61
+#: forum/forms.py:65
msgid "tags must be shorter than 20 characters"
-msgstr "las etiquetas deben ser menores a 20 caracteres"
+msgstr ""
-#: forum/forms.py:65
+#: forum/forms.py:69
msgid ""
"please use following characters in tags: letters 'a-z', numbers, and "
"characters '.-_#'"
msgstr ""
-"por favor use solo los siguientes caracteres en los nombres de etiquetas: "
-"letras 'a-z', números y caracteres '.-_#'"
-#: forum/forms.py:75 templates/index.html:57 templates/question.html:210
-#: templates/question.html.py:396 templates/questions.html:58
-#: templates/questions.html.py:70 templates/unanswered.html:48
-#: templates/unanswered.html.py:60
+#: forum/forms.py:79 templates/index.html:62 templates/index.html.py:74
+#: templates/post_contributor_info.html:7
+#: templates/question_summary_list_roll.html:26
+#: templates/question_summary_list_roll.html:38 templates/questions.html:96
+#: templates/questions.html.py:108 templates/unanswered.html:51
+#: templates/unanswered.html.py:63
msgid "community wiki"
-msgstr "wiki de comunidad"
+msgstr ""
-#: forum/forms.py:76
+#: forum/forms.py:80
msgid ""
"if you choose community wiki option, the question and answer do not generate "
"points and name of author will not be shown"
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:89
+#: forum/forms.py:96
msgid "update summary:"
-msgstr "resumen de modificación"
+msgstr ""
-#: forum/forms.py:90
+#: forum/forms.py:97
msgid ""
"enter a brief summary of your revision (e.g. fixed spelling, grammar, "
"improved style, this field is optional)"
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:98 forum/forms.py:159
-msgid "please choice a category"
-msgstr "por favor escoja una categoría"
+#: forum/forms.py:100
+msgid "Automatically accept user's contributions for the email updates"
+msgstr ""
-#: forum/forms.py:99 forum/forms.py:160
-msgid "Category"
-msgstr "Categoría"
+#: forum/forms.py:113
+msgid "Your name:"
+msgstr ""
-#: forum/forms.py:180
+#: forum/forms.py:114
+msgid "Email (not shared with anyone):"
+msgstr ""
+
+#: forum/forms.py:115
+msgid "Your message:"
+msgstr ""
+
+#: forum/forms.py:198
msgid "this email does not have to be linked to gravatar"
-msgstr "este email no tiene porque estar asociado a un Gravatar"
+msgstr ""
-#: forum/forms.py:181
+#: forum/forms.py:199
+msgid "Screen name"
+msgstr ""
+
+#: forum/forms.py:200
msgid "Real name"
-msgstr "Nombre real"
+msgstr ""
-#: forum/forms.py:182
+#: forum/forms.py:201
msgid "Website"
-msgstr "Sitio Web"
+msgstr ""
-#: forum/forms.py:183
+#: forum/forms.py:202
msgid "Location"
-msgstr "Ubicación"
+msgstr ""
-#: forum/forms.py:184
+#: forum/forms.py:203
msgid "Date of birth"
-msgstr "Fecha de nacimiento"
+msgstr ""
-#: forum/forms.py:184
+#: forum/forms.py:203
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"
+msgstr ""
-#: forum/forms.py:185 templates/authopenid/settings.html:21
+#: forum/forms.py:204 templates/authopenid/settings.html:21
msgid "Profile"
-msgstr "Perfil"
+msgstr ""
-#: forum/forms.py:212 forum/forms.py:213
+#: forum/forms.py:232 forum/forms.py:233
msgid "this email has already been registered, please use another one"
-msgstr "este email ya ha sido registrado, por favor use otro"
+msgstr ""
+
+#: forum/forms.py:239
+msgid "Choose email tag filter"
+msgstr ""
+
+#: forum/forms.py:254 forum/forms.py:255
+msgid "weekly"
+msgstr ""
+
+#: forum/forms.py:254 forum/forms.py:255
+msgid "no email"
+msgstr ""
+
+#: forum/forms.py:255
+msgid "daily"
+msgstr ""
+
+#: forum/forms.py:270
+msgid "Asked by me"
+msgstr ""
+
+#: forum/forms.py:273
+msgid "Answered by me"
+msgstr ""
+
+#: forum/forms.py:276
+msgid "Individually selected"
+msgstr ""
+
+#: forum/forms.py:279
+msgid "Entire forum (tag filtered)"
+msgstr ""
+
+#: forum/models.py:52
+msgid "Entire forum"
+msgstr ""
+
+#: forum/models.py:53
+msgid "Questions that I asked"
+msgstr ""
+
+#: forum/models.py:54
+msgid "Questions that I answered"
+msgstr ""
-#: forum/models.py:253
+#: forum/models.py:55
+msgid "Individually selected questions"
+msgstr ""
+
+#: forum/models.py:58
+msgid "Weekly"
+msgstr ""
+
+#: forum/models.py:59
+msgid "Daily"
+msgstr ""
+
+#: forum/models.py:60
+msgid "No email"
+msgstr ""
+
+#: forum/models.py:321
#, python-format
msgid "%(author)s modified the question"
-msgstr "%(author)s modificó la pregunta"
+msgstr ""
-#: forum/models.py:257
+#: forum/models.py:325
#, python-format
msgid "%(people)s posted %(new_answer_count)s new answers"
-msgstr "%(people)s publicaron %(new_answer_count)s nuevas respuestas"
+msgstr ""
-#: forum/models.py:262
+#: forum/models.py:330
#, python-format
msgid "%(people)s commented the question"
-msgstr "%(people)s comentarion la pregunta"
+msgstr ""
-#: forum/models.py:267
+#: forum/models.py:335
#, python-format
msgid "%(people)s commented answers"
-msgstr "%(people)s comentaron la respuesta"
+msgstr ""
-#: forum/models.py:269
+#: forum/models.py:337
#, python-format
msgid "%(people)s commented an answer"
-msgstr "%(people)s comentaron la respuesta"
+msgstr ""
+
+#: forum/models.py:368
+msgid "interesting"
+msgstr ""
-#: forum/models.py:313 forum/models.py:425
-msgid "revisions"
-msgstr "revisiones/"
+#: forum/models.py:368
+msgid "ignored"
+msgstr ""
-#: forum/models.py:448 templates/badges.html:51
+#: forum/models.py:538 templates/badges.html:53
msgid "gold"
-msgstr "oro"
+msgstr ""
-#: forum/models.py:449 templates/badges.html:59
+#: forum/models.py:539 templates/badges.html:61
msgid "silver"
-msgstr "plata"
+msgstr ""
-#: forum/models.py:450 templates/badges.html:66
+#: forum/models.py:540 templates/badges.html:68
msgid "bronze"
-msgstr "bronce"
+msgstr ""
+
+#: forum/urls.py:26
+msgid "upfiles/"
+msgstr ""
+
+#: forum/urls.py:30
+msgid "about/"
+msgstr ""
+
+#: forum/urls.py:31
+msgid "faq/"
+msgstr ""
+
+#: forum/urls.py:32
+msgid "privacy/"
+msgstr ""
+
+#: forum/urls.py:33
+msgid "logout/"
+msgstr ""
+
+#: forum/urls.py:34 forum/urls.py:35 forum/urls.py:36 forum/urls.py:53
+msgid "answers/"
+msgstr ""
+
+#: forum/urls.py:34 forum/urls.py:46 forum/urls.py:49 forum/urls.py:53
+msgid "comments/"
+msgstr ""
+
+#: forum/urls.py:35 forum/urls.py:40 forum/urls.py:75
+#: templates/user_info.html:45
+msgid "edit/"
+msgstr ""
+
+#: forum/urls.py:36 forum/urls.py:45
+msgid "revisions/"
+msgstr ""
+
+#: forum/urls.py:37 forum/urls.py:38 forum/urls.py:39 forum/urls.py:40
+#: forum/urls.py:41 forum/urls.py:42 forum/urls.py:43 forum/urls.py:44
+#: forum/urls.py:45 forum/urls.py:46 forum/urls.py:49
+msgid "questions/"
+msgstr ""
+
+#: forum/urls.py:38 forum/urls.py:85
+msgid "ask/"
+msgstr ""
+
+#: forum/urls.py:39
+msgid "unanswered/"
+msgstr ""
+
+#: forum/urls.py:41
+msgid "close/"
+msgstr ""
+
+#: forum/urls.py:42
+msgid "reopen/"
+msgstr ""
+
+#: forum/urls.py:43
+msgid "answer/"
+msgstr ""
+
+#: forum/urls.py:44
+msgid "vote/"
+msgstr ""
+
+#: forum/urls.py:47
+msgid "command/"
+msgstr ""
+
+#: forum/urls.py:58 forum/urls.py:59
+msgid "tags/"
+msgstr ""
+
+#: forum/urls.py:61 forum/urls.py:65
+msgid "mark-tag/"
+msgstr ""
+
+#: forum/urls.py:61
+msgid "interesting/"
+msgstr ""
+
+#: forum/urls.py:65
+msgid "ignored/"
+msgstr ""
+
+#: forum/urls.py:69
+msgid "unmark-tag/"
+msgstr ""
+
+#: forum/urls.py:73 forum/urls.py:75 forum/urls.py:76
+msgid "users/"
+msgstr ""
+
+#: forum/urls.py:74
+msgid "moderate-user/"
+msgstr ""
+
+#: forum/urls.py:77 forum/urls.py:78
+msgid "badges/"
+msgstr ""
+
+#: forum/urls.py:79
+msgid "messages/"
+msgstr ""
+
+#: forum/urls.py:79
+msgid "markread/"
+msgstr ""
+
+#: forum/urls.py:81
+msgid "nimda/"
+msgstr ""
+
+#: forum/urls.py:83
+msgid "upload/"
+msgstr ""
+
+#: forum/urls.py:84 forum/urls.py:85 forum/urls.py:86
+msgid "books/"
+msgstr ""
+
+#: forum/urls.py:87
+msgid "search/"
+msgstr ""
+
+#: forum/urls.py:88
+msgid "feedback/"
+msgstr ""
+
+#: forum/urls.py:89
+msgid "account/"
+msgstr ""
#: forum/user.py:16 templates/user_tabs.html:7
msgid "overview"
-msgstr "vista general"
+msgstr ""
#: forum/user.py:17
msgid "user profile"
-msgstr "perfil de usuario"
+msgstr ""
#: forum/user.py:18
msgid "user profile overview"
-msgstr "vista general del perfil de usuario"
+msgstr ""
#: forum/user.py:24 templates/user_tabs.html:9
msgid "recent activity"
-msgstr "actividades recientes"
+msgstr ""
#: forum/user.py:25
msgid "recent user activity"
-msgstr "actividades recientes del usuario"
+msgstr ""
#: forum/user.py:26
msgid "profile - recent activity"
-msgstr "perfil - actividades recientes"
+msgstr ""
#: forum/user.py:33 templates/user_tabs.html:13
msgid "responses"
-msgstr "respuestas"
+msgstr ""
#: forum/user.py:34 templates/user_tabs.html:12
msgid "comments and answers to others questions"
-msgstr "comentarios y respuestas a preguntas de otros"
+msgstr ""
#: forum/user.py:35
msgid "profile - responses"
-msgstr "perfil - respuestas"
+msgstr ""
-#: forum/user.py:42 templates/user_info.html:23 templates/users.html:26
+#: forum/user.py:42 templates/users.html:26
msgid "reputation"
-msgstr "reputación"
+msgstr ""
#: forum/user.py:43
msgid "user reputation in the community"
-msgstr "reputación del usuario en la comunidad"
+msgstr ""
#: forum/user.py:44
msgid "profile - user reputation"
-msgstr "perfil - reputación del usuario"
+msgstr ""
#: forum/user.py:50
msgid "favorite questions"
-msgstr "preguntas favoritas"
+msgstr ""
#: forum/user.py:51
msgid "users favorite questions"
-msgstr "preguntas favoritas de los usuarios"
+msgstr ""
#: forum/user.py:52
msgid "profile - favorite questions"
-msgstr "perfil - preguntas favoritas"
+msgstr ""
#: forum/user.py:59 templates/user_tabs.html:20
msgid "casted votes"
-msgstr "votos"
+msgstr ""
#: forum/user.py:60 templates/user_tabs.html:20
msgid "user vote record"
-msgstr "historial de votación"
+msgstr ""
#: forum/user.py:61
msgid "profile - votes"
-msgstr "perfil - votos"
+msgstr ""
-#: forum/user.py:68
-msgid "preferences"
-msgstr "preferencias"
+#: forum/user.py:68 templates/user_tabs.html:28
+msgid "email subscriptions"
+msgstr ""
#: forum/user.py:69 templates/user_tabs.html:27
-msgid "user preference settings"
-msgstr "preferencias del usuario"
+msgid "email subscription settings"
+msgstr ""
#: forum/user.py:70
-msgid "profile - user preferences"
-msgstr "perfil - preferencia de "
+msgid "profile - email subscriptions"
+msgstr ""
-#: forum/views.py:988
+#: forum/views.py:141
+msgid "Q&A forum feedback"
+msgstr ""
+
+#: forum/views.py:142
+msgid "Thanks for the feedback!"
+msgstr ""
+
+#: forum/views.py:150
+msgid "We look forward to hearing your feedback! Please, give it next time :)"
+msgstr ""
+
+#: forum/views.py:1080
#, python-format
-msgid "subscription saved, %(email)s needs validation"
-msgstr "subscripción guardada, %(email)s necesita validación"
+msgid "subscription saved, %(email)s needs validation, see %(details_url)s"
+msgstr ""
-#: forum/views.py:1914
+#: forum/views.py:1088
+msgid "email update frequency has been set to daily"
+msgstr ""
+
+#: forum/views.py:1965 forum/views.py:1969
+msgid "changes saved"
+msgstr ""
+
+#: forum/views.py:1975
+msgid "email updates canceled"
+msgstr ""
+
+#: forum/views.py:2142
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"
+msgstr ""
-#: forum/views.py:1916
+#: forum/views.py:2144
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:1918
+#: forum/views.py:2146
#, python-format
msgid "maximum upload file size is %sK"
-msgstr "tamaño máximo permitido es archivo %sK"
+msgstr ""
-#: forum/views.py:1920
+#: forum/views.py:2148
#, 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/management/commands/send_email_alerts.py:233
+msgid "email update message subject"
+msgstr ""
+
+#: forum/management/commands/send_email_alerts.py:234
+#, python-format
+msgid "%(name)s, this is an update message header for a question"
+msgid_plural "%(name)s, this is an update message header for %(num)d questions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: forum/management/commands/send_email_alerts.py:243
+#: forum/management/commands/send_email_alerts.py:258
+msgid "new question"
+msgstr ""
+
+#: forum/management/commands/send_email_alerts.py:268
+#, python-format
+msgid "There is also one question which was recently "
+msgid_plural ""
+"There are also %(num)d more questions which were recently updated "
+msgstr[0] ""
+msgstr[1] ""
+
+#: forum/management/commands/send_email_alerts.py:273
+msgid ""
+"Perhaps you could look up previously sent forum reminders in your mailbox."
+msgstr ""
+
+#: forum/management/commands/send_email_alerts.py:278
+#, python-format
+msgid ""
+"go to %(link)s to change frequency of email updates or %(email)s "
+"administrator"
+msgstr ""
-#: forum/templatetags/extra_tags.py:143 forum/templatetags/extra_tags.py:172
-#: templates/header.html:35
+#: forum/templatetags/extra_tags.py:163 forum/templatetags/extra_tags.py:192
+#: templates/header.html:33
msgid "badges"
-msgstr "distinciones"
+msgstr ""
-#: forum/templatetags/extra_tags.py:144 forum/templatetags/extra_tags.py:171
+#: forum/templatetags/extra_tags.py:164 forum/templatetags/extra_tags.py:191
msgid "reputation points"
-msgstr "puntos de reputación"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:247
+msgid "%b %d at %H:%M"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:249
+msgid "%b %d '%y at %H:%M"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:251
+msgid "2 days ago"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:253
+msgid "yesterday"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:255
+#, python-format
+msgid "%(hr)d hour ago"
+msgid_plural "%(hr)d hours ago"
+msgstr[0] ""
+msgstr[1] ""
+
+#: forum/templatetags/extra_tags.py:257
+#, python-format
+msgid "%(min)d min ago"
+msgid_plural "%(min)d mins ago"
+msgstr[0] ""
+msgstr[1] ""
-#: forum/templatetags/extra_tags.py:225
-msgid " ago"
-msgstr " atras"
+#: middleware/anon_user.py:33
+#, python-format
+msgid "first time greeting with %(url)s"
+msgstr ""
#: templates/404.html:24
msgid "Sorry, could not find the page you requested."
-msgstr "Disculpe, no se pudo encontrar la página que solicito."
+msgstr ""
#: templates/404.html:26
msgid "This might have happened for the following reasons:"
-msgstr "Esto puede haber sucedido por alguno de los siguientes motivos:"
+msgstr ""
#: templates/404.html:28
msgid "this question or answer has been deleted;"
-msgstr "esta pregunta o respuesta ha sido borrada;"
+msgstr ""
#: templates/404.html:29
msgid "url has error - please check it;"
-msgstr "la url tiene un error - por favor compruebelo;"
+msgstr ""
#: templates/404.html:30
msgid ""
"the page you tried to visit is protected or you don't have sufficient "
"points, see"
msgstr ""
-"La pagina que intentas acceder esta protegida o no tienes los puntos de "
-"reputación suficientes, ver"
#: templates/404.html:31
msgid "if you believe this error 404 should not have occured, please"
-msgstr "si consideras que este error 404 no debería haber sucedido, por favor"
+msgstr ""
#: templates/404.html:32
msgid "report this problem"
-msgstr "reporta este problema"
+msgstr ""
#: templates/404.html:41 templates/500.html:27
msgid "back to previous page"
-msgstr "volver a la página siguiente"
+msgstr ""
#: templates/404.html:42
msgid "see all questions"
-msgstr "ver todas las preguntas"
+msgstr ""
#: templates/404.html:43
msgid "see all tags"
-msgstr "ver todas las tags"
+msgstr ""
#: templates/500.html:22
msgid "sorry, system error"
-msgstr "lo sentimos, ha habido un error del sistema"
+msgstr ""
#: templates/500.html:24
msgid "system error log is recorded, error will be fixed as soon as possible"
msgstr ""
-"el error del sistema ha sido registrado, y será solucionado lo antes postible"
#: templates/500.html:25
msgid "please report the error to the site administrators if you wish"
-msgstr "por favor reportar el error al administrador de ser posible"
+msgstr ""
#: templates/500.html:28
msgid "see latest questions"
-msgstr "ver ultimas preguntas"
+msgstr ""
#: templates/500.html:29
msgid "see tags"
-msgstr "ver tags"
+msgstr ""
#: templates/about.html:6 templates/about.html.py:11
msgid "About"
-msgstr "Acerca de"
+msgstr ""
-#: templates/answer_edit.html:4 templates/answer_edit.html.py:47
+#: templates/answer_edit.html:5 templates/answer_edit.html.py:48
msgid "Edit answer"
-msgstr "Editar respuesta"
+msgstr ""
-#: templates/answer_edit.html:24 templates/answer_edit.html.py:27
-#: templates/ask.html:25 templates/ask.html.py:28 templates/question.html:43
-#: templates/question.html.py:46 templates/question_edit.html:27
+#: templates/answer_edit.html:25 templates/answer_edit.html.py:28
+#: templates/ask.html:26 templates/ask.html.py:29 templates/question.html:45
+#: templates/question.html.py:48 templates/question_edit.html:25
+#: templates/question_edit.html.py:28
msgid "hide preview"
-msgstr "ocultar previsualización"
+msgstr ""
-#: templates/answer_edit.html:27 templates/ask.html:28
-#: templates/question.html:46 templates/question_edit.html:27
+#: templates/answer_edit.html:28 templates/ask.html:29
+#: templates/question.html:48 templates/question_edit.html:28
msgid "show preview"
-msgstr "ver previsualización"
+msgstr ""
-#: templates/answer_edit.html:47 templates/question_edit.html:65
-#: templates/question_retag.html:52 templates/revisions_answer.html:36
-#: templates/revisions_question.html:36
+#: templates/answer_edit.html:48 templates/question_edit.html:66
+#: templates/question_retag.html:53 templates/revisions_answer.html:38
+#: templates/revisions_question.html:38
msgid "back"
-msgstr "volver"
+msgstr ""
-#: templates/answer_edit.html:52 templates/question_edit.html:70
-#: templates/revisions_answer.html:47 templates/revisions_question.html:47
+#: templates/answer_edit.html:53 templates/question_edit.html:71
+#: templates/revisions_answer.html:52 templates/revisions_question.html:52
msgid "revision"
-msgstr "revisión"
+msgstr ""
-#: templates/answer_edit.html:55 templates/question_edit.html:74
+#: templates/answer_edit.html:56 templates/question_edit.html:75
msgid "select revision"
-msgstr "seleccionar revisión"
+msgstr ""
-#: templates/answer_edit.html:62 templates/ask.html:94
-#: templates/question.html:468 templates/question_edit.html:91
+#: templates/answer_edit.html:63 templates/ask.html:97
+#: templates/question.html:442 templates/question_edit.html:92
msgid "Toggle the real time Markdown editor preview"
-msgstr "Activar la visualización en tiempo real de Markdown"
+msgstr ""
-#: templates/answer_edit.html:62 templates/ask.html:94
-#: templates/question.html:468 templates/question_edit.html:91
+#: templates/answer_edit.html:63 templates/ask.html:97
+#: templates/question.html:443 templates/question_edit.html:92
msgid "toggle preview"
-msgstr "Activar previsualización"
+msgstr ""
-#: templates/answer_edit.html:71 templates/question_edit.html:121
-#: templates/question_retag.html:73
+#: templates/answer_edit.html:72 templates/question_edit.html:124
+#: templates/question_retag.html:74
msgid "Save edit"
-msgstr "Guardar la edición"
+msgstr ""
-#: templates/answer_edit.html:72 templates/close.html:29
-#: templates/question_edit.html:122 templates/question_retag.html:74
-#: templates/reopen.html:30 templates/user_edit.html:83
-#: templates/authopenid/changeemail.html:34
+#: templates/answer_edit.html:73 templates/close.html:29
+#: templates/feedback.html:50 templates/question_edit.html:125
+#: templates/question_retag.html:75 templates/reopen.html:30
+#: templates/user_edit.html:87 templates/authopenid/changeemail.html:40
msgid "Cancel"
-msgstr "Cancelar"
+msgstr ""
#: templates/answer_edit_tips.html:4
msgid "answer tips"
-msgstr "sugerencias sobre respuestas"
+msgstr ""
#: templates/answer_edit_tips.html:7
msgid "please make your answer relevant to this community"
-msgstr "por favor, haz que tu respuesta sea relevante a esta comunidad"
+msgstr ""
#: templates/answer_edit_tips.html:10
msgid "try to give an answer, rather than engage into a discussion"
-msgstr "intenta dar una respuesta, más que entablar un debate o discusión"
+msgstr ""
#: templates/answer_edit_tips.html:13
msgid "please try to provide details"
-msgstr "por favor, intenta brindar detalles"
+msgstr ""
#: templates/answer_edit_tips.html:16 templates/question_edit_tips.html:13
msgid "be clear and concise"
-msgstr "ser claro y conciso"
+msgstr ""
-#: templates/answer_edit_tips.html:19 templates/question_edit_tips.html:16
+#: templates/answer_edit_tips.html:20 templates/question_edit_tips.html:17
msgid "see frequently asked questions"
-msgstr "ver preguntas frecuentes"
+msgstr ""
-#: templates/answer_edit_tips.html:24 templates/question_edit_tips.html:22
+#: templates/answer_edit_tips.html:26 templates/question_edit_tips.html:23
msgid "Markdown tips"
-msgstr "sugerencias de Markdown"
+msgstr ""
-#: templates/answer_edit_tips.html:27 templates/question_edit_tips.html:25
+#: templates/answer_edit_tips.html:29 templates/question_edit_tips.html:26
msgid "*italic* or __italic__"
-msgstr "*itálica* o __itálica__"
+msgstr ""
-#: templates/answer_edit_tips.html:30 templates/question_edit_tips.html:28
+#: templates/answer_edit_tips.html:32 templates/question_edit_tips.html:29
msgid "**bold** or __bold__"
-msgstr "**negrita** o __negrita__"
+msgstr ""
-#: templates/answer_edit_tips.html:33 templates/question_edit_tips.html:31
+#: templates/answer_edit_tips.html:35 templates/question_edit_tips.html:32
msgid "link"
-msgstr "enlace"
+msgstr ""
-#: templates/answer_edit_tips.html:33 templates/answer_edit_tips.html.py:37
-#: templates/question_edit_tips.html:31 templates/question_edit_tips.html:36
+#: templates/answer_edit_tips.html:35 templates/answer_edit_tips.html.py:39
+#: templates/question_edit_tips.html:32 templates/question_edit_tips.html:37
msgid "text"
-msgstr "texto"
+msgstr ""
-#: templates/answer_edit_tips.html:37 templates/question_edit_tips.html:36
+#: templates/answer_edit_tips.html:39 templates/question_edit_tips.html:37
msgid "image"
-msgstr "imagen"
+msgstr ""
-#: templates/answer_edit_tips.html:41 templates/question_edit_tips.html:40
+#: templates/answer_edit_tips.html:43 templates/question_edit_tips.html:41
msgid "numbered list:"
-msgstr "lista numerada"
+msgstr ""
-#: templates/answer_edit_tips.html:46 templates/question_edit_tips.html:45
+#: templates/answer_edit_tips.html:48 templates/question_edit_tips.html:46
msgid "basic HTML tags are also supported"
-msgstr "etiquetas básicas de HTML permitidas"
+msgstr ""
-#: templates/answer_edit_tips.html:49 templates/question_edit_tips.html:48
+#: templates/answer_edit_tips.html:52 templates/question_edit_tips.html:50
msgid "learn more about Markdown"
-msgstr "aprender mas sobre Markdown"
+msgstr ""
-#: templates/ask.html:4 templates/ask.html.py:60
+#: templates/ask.html:5 templates/ask.html.py:61
msgid "Ask a question"
-msgstr "Hacer una pregunta"
+msgstr ""
-#: templates/ask.html:67
+#: templates/ask.html:68
msgid "login to post question info"
msgstr ""
-"<span class='strong big'>Puedes comenzar a realizar tu pregunta de forma "
-"anonima</span> - actualmente no te encuentras Ingresado. Cuando envíes tu "
-"pregunta, serás redireccionado a la página de Ingreso/registro. Tu pregunta "
-"será guardada temporalemente y será enviada cuando ingreses. El proceso de "
-"Ingreso/Registro es muy simple. Ingresar lleva unos 30 segundos, registrarse "
-"por primera vez lleva un minuto."
-#: templates/ask.html:73
+#: templates/ask.html:74
#, python-format
-msgid "must have valid %(email)s to post"
+msgid ""
+"must have valid %(email)s to post, \n"
+" see %(email_validation_faq_url)s\n"
+" "
msgstr ""
-"<span class='strong big'>Parece ser que tu dirección de email, %(email)s no "
-"ha sido validada aún.</span> Para enviar mensajes debes verificar tu "
-"dirección de email, puedes ver <a href='/faq#validate'>más detalles aquí</"
-"a>. <br/> Puedes enviar tu pregunta ahora y validar tu email luego. Tu "
-"pregunta será guardada mientras tanto y publicada cuando valides tu email."
-#: templates/ask.html:107 templates/ask.html.py:114
-#: templates/question_edit.html:117
+#: templates/ask.html:112 templates/ask.html.py:119
+#: templates/question_edit.html:120
msgid "(required)"
-msgstr "(requerido)"
+msgstr ""
-#: templates/ask.html:121
+#: templates/ask.html:126
msgid "Login/signup to post your question"
-msgstr "Iniciar sesión/registrarse para publicar su pregunta"
+msgstr ""
-#: templates/ask.html:123
+#: templates/ask.html:128
msgid "Ask your question"
-msgstr "Haz tu pregunta"
+msgstr ""
#: templates/badge.html:6 templates/badge.html.py:17
msgid "Badge"
-msgstr "Distinción"
+msgstr ""
#: templates/badge.html:26
msgid "The users have been awarded with badges:"
-msgstr "Usuarios han sido galardonados con distinciones:"
+msgstr ""
#: templates/badges.html:6
msgid "Badges summary"
-msgstr "Resumen de distinciones"
+msgstr ""
-#: templates/badges.html:17 templates/user_stats.html:73
+#: templates/badges.html:17
msgid "Badges"
-msgstr "Distinciones"
+msgstr ""
#: 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."
+msgstr ""
#: templates/badges.html:22
+#, python-format
msgid ""
-"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 \n"
+" of times each type of badge has been awarded. Give us feedback at %"
+"(feedback_faq_url)s.\n"
+" "
msgstr ""
-"Debajo esta la lista de las distinciones disponibles y la cantidad de veces "
-"que han sido asignadas."
-#: templates/badges.html:48
+#: templates/badges.html:50
msgid "Community badges"
-msgstr "Distinciones de la comunidad"
+msgstr ""
-#: templates/badges.html:54
+#: templates/badges.html:56
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:62
+#: templates/badges.html:64
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:65
+#: templates/badges.html:67
msgid "bronze badge: often given as a special honor"
msgstr ""
-"distinción de bronce: con frecuencia entregada como reconocimiento especial."
-#: templates/badges.html:69
+#: templates/badges.html:71
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/book.html:7
msgid "reading channel"
-msgstr "canal de lectura"
+msgstr ""
#: templates/book.html:26
msgid "[author]"
-msgstr "[autor]"
+msgstr ""
#: templates/book.html:30
msgid "[publisher]"
-msgstr "[editorial]"
+msgstr ""
#: templates/book.html:34
msgid "[publication date]"
-msgstr "[fecha de publicación]"
+msgstr ""
#: templates/book.html:38
msgid "[price]"
-msgstr "[precio]"
+msgstr ""
#: templates/book.html:39
msgid "currency unit"
-msgstr "unidad de moneda"
+msgstr ""
#: templates/book.html:42
msgid "[pages]"
-msgstr "[páginas]"
+msgstr ""
#: templates/book.html:43
msgid "pages abbreviation"
-msgstr "abreviación de páginas"
+msgstr ""
#: templates/book.html:46
msgid "[tags]"
-msgstr "[etiquetas]"
+msgstr ""
#: templates/book.html:56
msgid "author blog"
-msgstr "blog del autor"
+msgstr ""
#: templates/book.html:62
msgid "book directory"
-msgstr "directorio del libro"
+msgstr ""
#: templates/book.html:66
msgid "buy online"
-msgstr "comprar en-linea"
+msgstr ""
#: templates/book.html:79
msgid "reader questions"
-msgstr "pregunta de lector"
+msgstr ""
#: templates/book.html:82
msgid "ask the author"
-msgstr "preguntar al autor"
+msgstr ""
#: templates/book.html:88 templates/book.html.py:93
#: templates/users_questions.html:18
msgid "this question was selected as favorite"
-msgstr "esta pregunta ha sido seleccionada como favorita"
+msgstr ""
#: templates/book.html:88 templates/book.html.py:93
#: templates/users_questions.html:11 templates/users_questions.html.py:18
msgid "number of times"
-msgstr "numero de veces"
+msgstr ""
-#: templates/book.html:105 templates/index.html:48 templates/questions.html:46
-#: templates/unanswered.html:37 templates/users_questions.html:32
+#: templates/book.html:105 templates/index.html:50
+#: templates/question_summary_list_roll.html:14 templates/questions.html:84
+#: templates/unanswered.html:39 templates/users_questions.html:32
msgid "votes"
-msgstr "votos"
+msgstr ""
#: templates/book.html:108
msgid "the answer has been accepted to be correct"
-msgstr "la respuesta ha sido aceptada como correcta"
+msgstr ""
-#: templates/book.html:115 templates/index.html:49 templates/questions.html:47
-#: templates/unanswered.html:38 templates/users_questions.html:42
+#: templates/book.html:115 templates/index.html:51
+#: templates/question_summary_list_roll.html:15 templates/questions.html:85
+#: templates/unanswered.html:40 templates/users_questions.html:40
msgid "views"
-msgstr "vistas"
+msgstr ""
-#: templates/book.html:125 templates/index.html:69 templates/question.html:500
-#: templates/questions.html:84 templates/questions.html.py:157
-#: templates/tags.html:49 templates/unanswered.html:75
-#: templates/unanswered.html.py:106 templates/users_questions.html:54
+#: templates/book.html:125 templates/index.html:106
+#: templates/question.html:488 templates/question_summary_list_roll.html:52
+#: templates/questions.html:140 templates/questions.html.py:257
+#: templates/tags.html:49 templates/unanswered.html:95
+#: templates/unanswered.html.py:122 templates/users_questions.html:52
msgid "using tags"
-msgstr "usando etiquetas"
+msgstr ""
#: templates/book.html:147
msgid "subscribe to book RSS feed"
-msgstr "suscribirse al RSS del libro"
+msgstr ""
-#: templates/book.html:147 templates/index.html:118
+#: templates/book.html:147 templates/index.html:157
msgid "subscribe to the questions feed"
-msgstr "suscribirse al agregado de noticias"
-
-#: templates/categories.html:6 templates/categories.html.py:29
-msgid "Category list"
-msgstr "Lista de Categorías"
-
-#: templates/categories.html:34 templates/tags.html:42
-msgid "Nothing found"
-msgstr "Nada encontrado"
-
-#: templates/categories.html:40
-#, fuzzy
-msgid "see questions that matches"
-msgstr "ver preguntas etiquetadas"
-
-#: templates/categories.html:40
-msgid "category "
-msgstr "categoría"
+msgstr ""
#: templates/close.html:6 templates/close.html.py:16
msgid "Close question"
-msgstr "Cerrar pregunta"
+msgstr ""
#: templates/close.html:19
msgid "Close the question"
-msgstr "Cerrar la pregunta"
+msgstr ""
#: templates/close.html:25
msgid "Reasons"
-msgstr "Razón"
+msgstr ""
#: templates/close.html:28
msgid "OK to close"
-msgstr "OK para cerrar"
+msgstr ""
#: templates/faq.html:11
msgid "Frequently Asked Questions "
-msgstr "Preguntas Frecuentes"
+msgstr ""
#: templates/faq.html:16
msgid "What kinds of questions can I ask here?"
-msgstr "¿Qué clase de preguntas puedo hacer aquí?"
+msgstr ""
#: 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?"
+msgstr ""
#: 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/faq.html:27
msgid "What should I avoid in my answers?"
-msgstr "¿Que debo evitar en mis respuestas?"
+msgstr ""
#: templates/faq.html:28
msgid ""
@@ -1218,42 +1455,32 @@ msgid ""
"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?"
+msgstr ""
#: templates/faq.html:33
msgid "The short answer is: <strong>you</strong>."
-msgstr "La respuesta corta es: <strong>tú</strong>"
+msgstr ""
#: templates/faq.html:34
msgid "This website is moderated by the users."
-msgstr "Este sitio es moderado por los usuarios."
+msgstr ""
#: 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?"
+msgstr ""
#: templates/faq.html:41
msgid "Rep system summary"
msgstr ""
-"Cuando una pregunta o respuesta es votada positivamente, el usuario que la "
-"realizo ganará algunos puntos, que llamamos \"puntos de reputación\". Estos "
-"puntos sirven a groso modo para medir la confianza que la comunidad le "
-"tiene. Diversas tareas de moderación son gradualmente asignadas a los "
-"usuarios basado en estos puntos de reputación."
#: templates/faq.html:42
msgid ""
@@ -1265,659 +1492,804 @@ msgid ""
"or answer. The table below explains reputation point requirements for each "
"type of moderation task."
msgstr ""
-"Por ejemplo, si haces una pregunta interesante o das una respuesta útil, tu "
-"adición será votada positivamente. Por otro lado, si la respuesta es fuera "
-"de lugar - será votada negativamente. Cada voto a favor genera <strong>10</"
-"strong> puntos, cada voto en contra restará <strong>2</strong> puntos. Hay "
-"un limite de <strong>200</strong> puntos que puedes acumular por pregunta o "
-"respuesta. La tabla debajo explica los puntos de reputación requeridos para "
-"cada tarea de moderación."
-#: templates/faq.html:53 templates/user_votes.html:14
+#: templates/faq.html:53 templates/user_votes.html:15
msgid "upvote"
-msgstr "votar positivo"
+msgstr ""
#: templates/faq.html:57
msgid "use tags"
-msgstr "etiquetas usadas"
+msgstr ""
#: templates/faq.html:62
msgid "add comments"
-msgstr "agregar comentarios"
+msgstr ""
-#: templates/faq.html:66 templates/user_votes.html:16
+#: templates/faq.html:66 templates/user_votes.html:17
msgid "downvote"
-msgstr "votar negativo"
+msgstr ""
#: templates/faq.html:69
msgid "open and close own questions"
-msgstr "abrir y cerrar sus propias preguntas"
+msgstr ""
#: templates/faq.html:73
msgid "retag questions"
-msgstr "re-etiquetar preguntas"
+msgstr ""
-#: templates/faq.html:77
+#: templates/faq.html:78
msgid "edit community wiki questions"
-msgstr "editar preguntas de la wiki comunitaria"
+msgstr ""
-#: templates/faq.html:81
+#: templates/faq.html:83
msgid "edit any answer"
-msgstr "editar cualquier pregunta"
+msgstr ""
-#: templates/faq.html:85
+#: templates/faq.html:87
msgid "open any closed question"
-msgstr "abrir cualquier pregunta cerrada"
+msgstr ""
-#: templates/faq.html:89
+#: templates/faq.html:91
msgid "delete any comment"
-msgstr "borrar cualquier comentario"
+msgstr ""
-#: templates/faq.html:93
+#: templates/faq.html:95
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:100
+#: templates/faq.html:102
msgid "how to validate email title"
-msgstr "¿Cómo validar mi correo electrónico?"
+msgstr ""
-#: templates/faq.html:102
-msgid "how to validate email info"
-msgstr ""
-"<form style='margin:0;padding:0;' action='/email/sendkey/'><p><span class="
-"\"bigger strong\">¿Cómo?</span> Si acabas de asignar o cambiar tu correo "
-"electrónico - <strong>verifica tu casilla de mensajes y clickea en el link "
-"incluido</strong>. <br/> El link contiene una clave generada especificamente "
-"para ti. <button style='display:inline' type='submit'><strong>get a new key</"
-"strong></button> y vuelve a revisar tu casilla de mensajes.</p></form><span "
-"class=\"bigger strong\">¿Porqué?</span> La validación del email es requerida "
-"para estar seguros the que <strong>solo tu puedes enviar mensajes</strong> "
-"bajo tu concentimiento y para <strong>minimizar el spam</strong>.<br/> Con "
-"tu email podrás <strong>suscribirte a actualizaciones</strong> en las "
-"preguntas mas interesantes. También, cuando te registras por primera vez - "
-"se crea un imagen personal única de <a href='/"
-"faq#gravatar'><strong>gravatar</strong></a>."
-
-#: templates/faq.html:106
+#: templates/faq.html:104
+#, python-format
+msgid ""
+"how to validate email info with %(send_email_key_url)s %(gravatar_faq_url)s"
+msgstr ""
+
+#: templates/faq.html:108
msgid "what is gravatar"
-msgstr "¿Qué es gravatar?"
+msgstr ""
-#: templates/faq.html:107
+#: templates/faq.html:109
msgid "gravatar faq info"
msgstr ""
-"<strong>Gravatar</strong> significa <strong>g</strong>lobalmente <strong>r</"
-"strong>econocido <strong>avatar</strong> - tu imagen única asociada a tu "
-"email. Es simplemente una imagen que se muestra junto con tus mensajes en "
-"sitios que soportan gravatar. Por defecto gravatar aparece como un cuadrado "
-"rellenado con figuras parecidas a copos de nieve. Puedes <strong>seleccionar "
-"tu imagen</strong> en <a href='http://gravatar.com'><strong>gravatar.com</"
-"strong></a>"
-#: templates/faq.html:110
+#: templates/faq.html:112
msgid "To register, do I need to create new password?"
-msgstr "¿Para registrarme, debo crearme una cuenta?"
+msgstr ""
-#: templates/faq.html:111
+#: templates/faq.html:113
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:112
+#: templates/faq.html:114
msgid "Login now!"
-msgstr "Ingresa ahora!"
+msgstr ""
-#: templates/faq.html:117
+#: templates/faq.html:119
msgid "Why other people can edit my questions/answers?"
-msgstr "¿Porqué otras personas pueden editar mis preguntas y respuestas?"
+msgstr ""
-#: templates/faq.html:118
+#: templates/faq.html:120
msgid "Goal of this site is..."
msgstr ""
-"El objetivo de este sitio es generar contenido valioso mediante preguntas y "
-"respuestas, de forma colaborativa. "
-#: templates/faq.html:118
+#: templates/faq.html:120
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 "
-"acumulado."
-#: templates/faq.html:119
+#: templates/faq.html:121
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:123
+#: templates/faq.html:125
msgid "Still have questions?"
-msgstr "¿Aún tienes preguntas?"
+msgstr ""
-#: templates/faq.html:124
-msgid "Please ask your question, help make our community better!"
-msgstr "Por favor haz tu pregunta, ¡ayudanos a mejorar nuestra comunidad!"
+#: templates/faq.html:126
+#, python-format
+msgid ""
+"Please ask your question at %(ask_question_url)s, help make our community "
+"better!"
+msgstr ""
-#: templates/faq.html:126 templates/header.html:29 templates/header.html.py:63
+#: templates/faq.html:128 templates/header.html:27 templates/header.html.py:61
msgid "questions"
-msgstr "preguntas"
+msgstr ""
-#: templates/faq.html:126 templates/index.html:123
+#: templates/faq.html:128 templates/index.html:162
msgid "."
-msgstr "."
+msgstr ""
+
+#: templates/feedback.html:6
+msgid "Feedback"
+msgstr ""
+
+#: templates/feedback.html:11
+msgid "Give us your feedback!"
+msgstr ""
+
+#: templates/feedback.html:17
+#, python-format
+msgid ""
+"\n"
+" <span class='big strong'>Dear %(user_name)s</span>, we look "
+"forward to hearing your feedback. \n"
+" Please type and send us your message below.\n"
+" "
+msgstr ""
+
+#: templates/feedback.html:24
+msgid ""
+"\n"
+" <span class='big strong'>Dear visitor</span>, we look forward to "
+"hearing your feedback.\n"
+" Please type and send us your message below.\n"
+" "
+msgstr ""
-#: templates/footer.html:7 templates/header.html:14 templates/index.html:83
+#: templates/feedback.html:41
+msgid "(this field is required)"
+msgstr ""
+
+#: templates/feedback.html:49
+msgid "Send Feedback"
+msgstr ""
+
+#: templates/footer.html:8 templates/header.html:13 templates/index.html:120
msgid "about"
-msgstr "acerca de nosotros"
+msgstr ""
-#: templates/footer.html:8 templates/header.html:15 templates/index.html:84
-#: templates/question_edit_tips.html:16
+#: templates/footer.html:9 templates/header.html:14 templates/index.html:121
+#: templates/question_edit_tips.html:17
msgid "faq"
-msgstr "preguntas frecuentes"
+msgstr ""
-#: templates/footer.html:9
+#: templates/footer.html:10
msgid "blog"
-msgstr "blog"
+msgstr ""
-#: templates/footer.html:10
+#: templates/footer.html:11
msgid "contact us"
-msgstr "contactenos"
+msgstr ""
-#: templates/footer.html:11
+#: templates/footer.html:12
msgid "privacy policy"
-msgstr "código de privacidad"
+msgstr ""
-#: templates/footer.html:12
+#: templates/footer.html:21
msgid "give feedback"
-msgstr "envía comentarios"
-
-#: templates/footer.html:18
-msgid "current revision"
-msgstr "revisión actual"
+msgstr ""
-#: templates/header.html:10
+#: templates/header.html:9
msgid "logout"
-msgstr "salir"
+msgstr ""
-#: templates/header.html:12 templates/authopenid/signup.html:41
+#: templates/header.html:11
msgid "login"
-msgstr "entrar"
+msgstr ""
-#: templates/header.html:23
+#: templates/header.html:21
msgid "back to home page"
-msgstr "volver página principal"
+msgstr ""
-#: templates/header.html:31 templates/header.html.py:65
+#: templates/header.html:29 templates/header.html.py:63
msgid "users"
-msgstr "usuarios"
+msgstr ""
-#: templates/header.html:33
+#: templates/header.html:31
msgid "books"
-msgstr "libros"
+msgstr ""
-#: templates/header.html:36
+#: templates/header.html:34
msgid "unanswered questions"
-msgstr "sin respuesta"
+msgstr ""
-#: templates/header.html:40
+#: templates/header.html:38
msgid "my profile"
-msgstr "mi perfil"
+msgstr ""
-#: templates/header.html:44
+#: templates/header.html:42
msgid "ask a question"
-msgstr "hacer una pregunta"
+msgstr ""
-#: templates/header.html:59
+#: templates/header.html:57
msgid "search"
-msgstr "buscar"
+msgstr ""
-#: templates/index.html:7
+#: templates/index.html:8
msgid "Home"
-msgstr "Inicio"
+msgstr ""
-#: templates/index.html:22 templates/questions.html:7
+#: templates/index.html:25 templates/questions.html:8
msgid "Questions"
-msgstr "Preguntas"
+msgstr ""
-#: templates/index.html:24
+#: templates/index.html:27
msgid "last updated questions"
-msgstr "ultimas preguntas actualizadas"
+msgstr ""
-#: templates/index.html:24 templates/questions.html:25
-#: templates/unanswered.html:20
+#: templates/index.html:27 templates/questions.html:51
+#: templates/unanswered.html:21
msgid "newest"
-msgstr "más nuevas"
+msgstr ""
+
+#: templates/index.html:28 templates/questions.html:52
+msgid "most recently updated questions"
+msgstr ""
+
+#: templates/index.html:28 templates/questions.html:52
+msgid "active"
+msgstr ""
-#: templates/index.html:25 templates/questions.html:27
+#: templates/index.html:29 templates/questions.html:53
msgid "hottest questions"
-msgstr "preguntas calientes"
+msgstr ""
-#: templates/index.html:25 templates/questions.html:27
+#: templates/index.html:29 templates/questions.html:53
msgid "hottest"
-msgstr "más calientes"
+msgstr ""
-#: templates/index.html:26 templates/questions.html:28
+#: templates/index.html:30 templates/questions.html:54
msgid "most voted questions"
-msgstr "preguntas más votadas"
+msgstr ""
-#: templates/index.html:26 templates/questions.html:28
+#: templates/index.html:30 templates/questions.html:54
msgid "most voted"
-msgstr "más votadas"
+msgstr ""
-#: templates/index.html:27
+#: templates/index.html:31
msgid "all questions"
-msgstr "todas las preguntas"
+msgstr ""
-#: templates/index.html:47 templates/questions.html:45
-#: templates/unanswered.html:36 templates/users_questions.html:37
+#: templates/index.html:49 templates/question_summary_list_roll.html:13
+#: templates/questions.html:83 templates/unanswered.html:38
+#: templates/users_questions.html:36
msgid "answers"
-msgstr "respuestas"
+msgstr ""
-#: templates/index.html:69 templates/question.html:500
-#: templates/questions.html:84 templates/questions.html.py:157
-#: templates/tags.html:49 templates/unanswered.html:75
-#: templates/unanswered.html.py:106 templates/users_questions.html:54
+#: templates/index.html:81 templates/index.html.py:95
+#: templates/questions.html:115 templates/questions.html.py:129
+#: templates/unanswered.html:70 templates/unanswered.html.py:84
+msgid "Posted:"
+msgstr ""
+
+#: templates/index.html:84 templates/index.html.py:89
+#: templates/questions.html:118 templates/questions.html.py:123
+#: templates/unanswered.html:73 templates/unanswered.html.py:78
+msgid "Updated:"
+msgstr ""
+
+#: templates/index.html:106 templates/question.html:488
+#: templates/question_summary_list_roll.html:52 templates/questions.html:140
+#: templates/questions.html.py:257 templates/tags.html:49
+#: templates/unanswered.html:95 templates/unanswered.html.py:122
+#: templates/users_questions.html:52
msgid "see questions tagged"
-msgstr "ver preguntas etiquetadas"
+msgstr ""
-#: templates/index.html:80
+#: templates/index.html:117
msgid "welcome to website"
-msgstr "bienvenido a sitio"
+msgstr ""
-#: templates/index.html:89
+#: templates/index.html:128
msgid "Recent tags"
-msgstr "Etiquetas recientes"
+msgstr ""
-#: templates/index.html:94 templates/question.html:125
+#: templates/index.html:133 templates/question.html:135
#, python-format
msgid "see questions tagged '%(tagname)s'"
-msgstr "ver preguntas etiquetadas '%(tagname)s'"
+msgstr ""
-#: templates/index.html:97 templates/index.html.py:123
+#: templates/index.html:136 templates/index.html.py:162
msgid "popular tags"
-msgstr "etiquetas populares"
+msgstr ""
-#: templates/index.html:102
+#: templates/index.html:141
msgid "Recent awards"
-msgstr "Reconocimientos recientes"
+msgstr ""
-#: templates/index.html:108
+#: templates/index.html:147
msgid "given to"
-msgstr "dados a"
+msgstr ""
-#: templates/index.html:113
+#: templates/index.html:152
msgid "all awards"
-msgstr "todos los reconocimientos"
+msgstr ""
-#: templates/index.html:118
+#: templates/index.html:157
msgid "subscribe to last 30 questions by RSS"
-msgstr "suscribirse a las últimas 30 preguntas por RSS"
+msgstr ""
-#: templates/index.html:123
+#: templates/index.html:162
msgid "Still looking for more? See"
-msgstr "¿Aún sigues buscando más? Ver"
+msgstr ""
-#: templates/index.html:123
+#: templates/index.html:162
msgid "complete list of questions"
-msgstr "lista completa de preguntas"
+msgstr ""
-#: templates/index.html:123
+#: templates/index.html:162 templates/authopenid/signup.html:18
msgid "or"
-msgstr "ó"
+msgstr ""
-#: templates/index.html:123
+#: templates/index.html:162
msgid "Please help us answer"
-msgstr "Ayudanos a responder"
+msgstr ""
-#: templates/index.html:123
+#: templates/index.html:162
msgid "list of unanswered questions"
-msgstr "lista de preguntas sin respuesta"
+msgstr ""
-#: templates/logout.html:6 templates/logout.html.py:17
+#: templates/logout.html:6 templates/logout.html.py:16
msgid "Logout"
-msgstr "Salir"
+msgstr ""
-#: templates/logout.html:20
+#: templates/logout.html:19
msgid ""
"As a registered user you can login with your OpenID, log out of the site or "
"permanently remove your account."
msgstr ""
-"Como usuario registrado puedes ingresar con tu OpenID, salir del sitio o "
-"eliminar de forma permanente tu cuenta."
-#: templates/logout.html:21
+#: templates/logout.html:20
msgid "Logout now"
-msgstr "Salir ahora"
+msgstr ""
#: templates/pagesize.html:6
msgid "posts per page"
-msgstr "entradas por página"
+msgstr ""
#: templates/paginator.html:6 templates/paginator.html.py:7
msgid "previous"
-msgstr "previo"
+msgstr ""
#: templates/paginator.html:19
msgid "current page"
-msgstr "página actúal"
+msgstr ""
#: templates/paginator.html:22 templates/paginator.html.py:29
msgid "page number "
-msgstr "número de página"
+msgstr ""
#: templates/paginator.html:22 templates/paginator.html.py:29
msgid "number - make blank in english"
-msgstr " "
+msgstr ""
#: templates/paginator.html:33
msgid "next page"
-msgstr "próxima página"
+msgstr ""
+
+#: templates/post_contributor_info.html:9
+#, python-format
+msgid ""
+"\n"
+" one revision\n"
+" "
+msgid_plural ""
+"\n"
+" %(rev_count)s revisions\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/post_contributor_info.html:19
+msgid "asked"
+msgstr ""
+
+#: templates/post_contributor_info.html:22
+msgid "answered"
+msgstr ""
+
+#: templates/post_contributor_info.html:24
+msgid "posted"
+msgstr ""
+
+#: templates/post_contributor_info.html:45
+msgid "updated"
+msgstr ""
#: templates/privacy.html:6 templates/privacy.html.py:11
msgid "Privacy policy"
-msgstr "Privacidad"
+msgstr ""
#: templates/privacy.html:15
msgid "general message about privacy"
-msgstr "mensaje de privacidad"
+msgstr ""
#: templates/privacy.html:18
msgid "Site Visitors"
-msgstr "Visitantes del Sitio"
+msgstr ""
#: templates/privacy.html:20
msgid "what technical information is collected about visitors"
-msgstr "que información es recolectada sobre los usuarios"
+msgstr ""
#: templates/privacy.html:23
msgid "Personal Information"
-msgstr "Información Personal"
+msgstr ""
#: templates/privacy.html:25
msgid "details on personal information policies"
-msgstr "describir código de manejo de la información personal"
+msgstr ""
#: templates/privacy.html:28
msgid "Other Services"
-msgstr "Otros servicios"
+msgstr ""
#: templates/privacy.html:30
msgid "details on sharing data with third parties"
-msgstr "detalles sobre compartir información con terceros"
+msgstr ""
#: templates/privacy.html:35
msgid "cookie policy details"
-msgstr "uso de cookies"
+msgstr ""
#: templates/privacy.html:37
msgid "Policy Changes"
-msgstr "Cambios de Códigos"
+msgstr ""
#: templates/privacy.html:38
msgid "how privacy policies can be changed"
-msgstr "como pueden ser cambiados los códigos de privacidad"
+msgstr ""
-#: templates/question.html:72 templates/question.html.py:73
-#: templates/question.html:85 templates/question.html.py:87
+#: templates/question.html:77 templates/question.html.py:78
+#: templates/question.html:94 templates/question.html.py:96
msgid "i like this post (click again to cancel)"
-msgstr "Me gusta esta entrada (clickear devuelta para cancelar)"
+msgstr ""
-#: templates/question.html:75 templates/question.html.py:89
-#: templates/question.html:290
+#: templates/question.html:80 templates/question.html.py:98
+#: templates/question.html:257
msgid "current number of votes"
-msgstr "número actual de votos"
+msgstr ""
-#: templates/question.html:80 templates/question.html.py:81
-#: templates/question.html:94 templates/question.html.py:95
+#: templates/question.html:89 templates/question.html.py:90
+#: templates/question.html:103 templates/question.html.py:104
msgid "i dont like this post (click again to cancel)"
-msgstr "No me gusta esta entrada (clickear devuelta para cancelar)"
+msgstr ""
-#: templates/question.html:100 templates/question.html.py:101
+#: templates/question.html:109 templates/question.html.py:110
msgid "mark this question as favorite (click again to cancel)"
-msgstr "marcar esta pregunta como favorita (clickear devuelta para cancelar)"
+msgstr ""
-#: templates/question.html:107 templates/question.html.py:108
+#: templates/question.html:116 templates/question.html.py:117
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:128 templates/questions.html:87
-msgid "Category: "
-msgstr "Categoría: "
-#: templates/question.html:135 templates/question.html.py:323
-#: templates/revisions_answer.html:53 templates/revisions_question.html:53
+#: templates/question.html:140 templates/question.html.py:294
+#: templates/revisions_answer.html:58 templates/revisions_question.html:58
msgid "edit"
-msgstr "editar"
-
-#: templates/question.html:139 templates/question.html.py:333
-msgid "delete"
-msgstr "borrar"
+msgstr ""
-#: templates/question.html:144
+#: templates/question.html:145
msgid "reopen"
-msgstr "re-abrir"
+msgstr ""
#: templates/question.html:149
msgid "close"
-msgstr "cerrar"
+msgstr ""
-#: templates/question.html:155 templates/question.html.py:346
+#: templates/question.html:155 templates/question.html.py:300
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:156 templates/question.html.py:347
+#: templates/question.html:156 templates/question.html.py:301
msgid "flag offensive"
-msgstr "marcar como ofensivo"
-
-#: templates/question.html:168 templates/question.html.py:356
-#: templates/revisions_answer.html:65 templates/revisions_question.html:65
-msgid "updated"
-msgstr "actualizado"
+msgstr ""
-#: templates/question.html:217 templates/question.html.py:403
-#: templates/revisions_answer.html:63 templates/revisions_question.html:63
-msgid "asked"
-msgstr "preguntado"
+#: templates/question.html:164 templates/question.html.py:312
+msgid "delete"
+msgstr ""
-#: templates/question.html:247 templates/question.html.py:430
-msgid "comments"
-msgstr "comentarios"
+#: templates/question.html:182 templates/question.html.py:332
+msgid "delete this comment"
+msgstr ""
-#: templates/question.html:248 templates/question.html.py:431
+#: templates/question.html:193 templates/question.html.py:343
+#: templates/question.html:367
msgid "add comment"
-msgstr "agregar comentario"
+msgstr ""
+
+#: templates/question.html:197
+#, python-format
+msgid ""
+"\n"
+" see <strong>one</strong> more \n"
+" "
+msgid_plural ""
+"\n"
+" see <strong>%(counter)s</strong> "
+"more\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/question.html:261
-msgid "The question has been closed for the following reason"
-msgstr "La pregunta fue cerrada por el siguiente motivo "
+#: templates/question.html:203
+#, python-format
+msgid ""
+"\n"
+" see <strong>one</strong> more "
+"comment\n"
+" "
+msgid_plural ""
+"\n"
+" see <strong>%(counter)s</strong> "
+"more comments\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/question.html:261
-msgid "by"
-msgstr "por"
+#: templates/question.html:219
+#, python-format
+msgid ""
+"The question has been closed for the following reason \"%(close_reason)s\" by"
+msgstr ""
-#: templates/question.html:263
-msgid "close date "
-msgstr "fecha de cierre"
+#: templates/question.html:221
+#, python-format
+msgid "close date %(closed_at)s"
+msgstr ""
-#: templates/question.html:270 templates/user_stats.html:13
-msgid "Answers"
-msgstr "Respuestas"
+#: templates/question.html:229
+#, python-format
+msgid ""
+"\n"
+" One Answer:\n"
+" "
+msgid_plural ""
+"\n"
+" %(counter)s Answers:\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/question.html:272
+#: templates/question.html:237
msgid "oldest answers will be shown first"
-msgstr "la respuesta mas vieja será mostrada primero"
+msgstr ""
-#: templates/question.html:272
+#: templates/question.html:237
msgid "oldest answers"
-msgstr "pregunta más vieja"
+msgstr ""
-#: templates/question.html:273
+#: templates/question.html:239
msgid "newest answers will be shown first"
-msgstr "preguntas más nuevas serán mostradas primero"
+msgstr ""
-#: templates/question.html:273
+#: templates/question.html:239
msgid "newest answers"
-msgstr "más nuevas"
+msgstr ""
-#: templates/question.html:274
+#: templates/question.html:241
msgid "most voted answers will be shown first"
-msgstr "las preguntas más votadas serán mostradas primero"
+msgstr ""
-#: templates/question.html:274
+#: templates/question.html:241
msgid "popular answers"
-msgstr "respuestas populares"
+msgstr ""
-#: templates/question.html:288 templates/question.html.py:289
+#: templates/question.html:255 templates/question.html.py:256
msgid "i like this answer (click again to cancel)"
-msgstr "me gusta esta respuesta (clickear devuelta para cancelar)"
+msgstr ""
-#: templates/question.html:295 templates/question.html.py:296
+#: templates/question.html:262 templates/question.html.py:263
msgid "i dont like this answer (click again to cancel)"
-msgstr "no me gusta esta respuesta (clickear devuelta para cancelar)"
+msgstr ""
-#: templates/question.html:301 templates/question.html.py:302
+#: templates/question.html:268 templates/question.html.py:269
msgid "mark this answer as favorite (click again to undo)"
-msgstr "marcar esta respuesta como favorita (clickear devuelta para deshacer)"
+msgstr ""
-#: templates/question.html:307 templates/question.html.py:308
+#: templates/question.html:274 templates/question.html.py:275
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:330
-msgid "undelete"
-msgstr "deshacer eliminar"
+msgstr ""
-#: templates/question.html:340
+#: templates/question.html:288
msgid "answer permanent link"
-msgstr "enlace permanente a respuesta"
+msgstr ""
-#: templates/question.html:341
+#: templates/question.html:289
msgid "permanent link"
-msgstr "enlace permanente"
+msgstr ""
+
+#: templates/question.html:312
+msgid "undelete"
+msgstr ""
+
+#: templates/question.html:347
+#, python-format
+msgid ""
+"\n"
+" see <strong>one</"
+"strong> more \n"
+" "
+msgid_plural ""
+"\n"
+" see <strong>%"
+"(counter)s</strong> more\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/question.html:353
+#, python-format
+msgid ""
+"\n"
+" see <strong>one</"
+"strong> more comment\n"
+" "
+msgid_plural ""
+"\n"
+" see <strong>%"
+"(counter)s</strong> more comments\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/question.html:366
+msgid "comments"
+msgstr ""
-#: templates/question.html:454
+#: templates/question.html:386 templates/question.html.py:389
+msgid "Notify me once a day when there are any new answers"
+msgstr ""
+
+#: templates/question.html:392
+msgid "Notify me weekly when there are any new answers"
+msgstr ""
+
+#: templates/question.html:397
+#, python-format
+msgid ""
+"\n"
+" You can always adjust frequency of email updates from your %"
+"(profile_url)s\n"
+" "
+msgstr ""
+
+#: templates/question.html:404
+msgid "once you sign in you will be able to subscribe for any updates here"
+msgstr ""
+
+#: templates/question.html:415
msgid "Your answer"
-msgstr "Tu respuesta"
+msgstr ""
-#: templates/question.html:457
+#: templates/question.html:417
+msgid "Be the first one to answer this question!"
+msgstr ""
+
+#: templates/question.html:423
msgid "you can answer anonymously and then login"
-msgstr "puedes responder de forma anónima y luego ingresar"
+msgstr ""
-#: templates/question.html:480
-msgid "Answer the question"
-msgstr "Responde la pregunta"
+#: templates/question.html:427
+msgid "answer your own question only to give an answer"
+msgstr ""
-#: templates/question.html:482
-msgid "Notify me daily if there are any new answers."
-msgstr "Notificarme diariamente si hay nuevas respuestas."
+#: templates/question.html:429
+msgid "please only give an answer, no discussions"
+msgstr ""
-#: templates/question.html:484
-msgid "once you sign in you will be able to subscribe for any updates here"
+#: templates/question.html:465
+msgid "Login/Signup to Post Your Answer"
+msgstr ""
+
+#: templates/question.html:468
+msgid "Answer Your Own Question"
+msgstr ""
+
+#: templates/question.html:470
+msgid "Answer the question"
msgstr ""
-"una vez que hayas ingresado podrás suscribirte a cualquiera de las "
-"actualizaciones aquí."
-#: templates/question.html:495
+#: templates/question.html:483
msgid "Question tags"
-msgstr "Tags de la pregunta"
+msgstr ""
-#: templates/question.html:505
+#: templates/question.html:493
msgid "question asked"
-msgstr "pregunta preguntada"
-
-#: templates/question.html:505 templates/question.html.py:511
-#: templates/user_info.html:51
-msgid "ago"
-msgstr " atras"
+msgstr ""
-#: templates/question.html:508
+#: templates/question.html:496
msgid "question was seen"
-msgstr "la pregunta fue vista"
+msgstr ""
-#: templates/question.html:508
+#: templates/question.html:496
msgid "times"
-msgstr "veces"
+msgstr ""
-#: templates/question.html:511
+#: templates/question.html:499
msgid "last updated"
-msgstr "última vez actualizada"
+msgstr ""
-#: templates/question.html:516
+#: templates/question.html:504
msgid "Related questions"
-msgstr "Preguntas relacionadas"
+msgstr ""
-#: templates/question_edit.html:4 templates/question_edit.html.py:65
+#: templates/question_edit.html:5 templates/question_edit.html.py:66
msgid "Edit question"
-msgstr "Editar pregunta"
+msgstr ""
#: templates/question_edit_tips.html:4
msgid "question tips"
-msgstr "sugerencias sobre pregunta"
+msgstr ""
#: templates/question_edit_tips.html:7
msgid "please ask a relevant question"
-msgstr "por favor hacer preguntas relevantes"
+msgstr ""
#: templates/question_edit_tips.html:10
msgid "please try provide enough details"
-msgstr "intente proveer suficientes detalles"
+msgstr ""
-#: templates/question_retag.html:3 templates/question_retag.html.py:52
+#: templates/question_retag.html:4 templates/question_retag.html.py:53
msgid "Change tags"
-msgstr "Cambiar etiquetas"
+msgstr ""
-#: templates/question_retag.html:39
+#: templates/question_retag.html:40
msgid "up to 5 tags, less than 20 characters each"
-msgstr "hasta 5 etiquetas, menos de 20 caracteres cada una"
+msgstr ""
-#: templates/question_retag.html:82
+#: templates/question_retag.html:83
msgid "Why use and modify tags?"
-msgstr "¿Porqué usar y modificar etiquetas?"
+msgstr ""
-#: templates/question_retag.html:85
+#: templates/question_retag.html:86
msgid "tags help us keep Questions organized"
-msgstr "las etiquetas nos permiten mantener las Preguntas organizadas"
+msgstr ""
-#: templates/question_retag.html:91
+#: templates/question_retag.html:94
msgid "tag editors receive special awards from the community"
msgstr ""
-"los editores de etiquetas reciben distinciones especiales de la comunidad"
-#: templates/questions.html:23
+#: templates/questions.html:28 templates/questions.html.py:32
msgid "Found by tags"
-msgstr "Encontradas por etiqueta"
+msgstr ""
-#: templates/questions.html:23
+#: templates/questions.html:28 templates/questions.html.py:38
msgid "Found by title"
-msgstr "Encontradas por título"
+msgstr ""
-#: templates/questions.html:23
+#: templates/questions.html:28 templates/questions.html.py:44
msgid "All questions"
-msgstr "Todas las preguntas"
+msgstr ""
+
+#: templates/questions.html:36
+msgid "Search results"
+msgstr ""
-#: templates/questions.html:25 templates/unanswered.html:20
+#: templates/questions.html:42 templates/unanswered.html:8
+#: templates/unanswered.html.py:19
+msgid "Unanswered questions"
+msgstr ""
+
+#: templates/questions.html:51 templates/unanswered.html:21
msgid "most recently asked questions"
-msgstr "preguntas hechas más recientemente"
+msgstr ""
-#: templates/questions.html:26
-msgid "most recently updated questions"
-msgstr "preguntas actualizadas más recientemente"
+#: templates/questions.html:143
+msgid "Category: "
+msgstr ""
-#: templates/questions.html:26
-msgid "active"
-msgstr "actividad"
+#: templates/questions.html:149
+msgid "Did not find anything?"
+msgstr ""
+
+#: templates/questions.html:152
+msgid "Did not find what you were looking for?"
+msgstr ""
-#: templates/questions.html:110
+#: templates/questions.html:154
+msgid "Please, post your question!"
+msgstr ""
+
+#: templates/questions.html:169
#, python-format
msgid ""
"\n"
@@ -1928,15 +2300,9 @@ msgid_plural ""
"\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:117
+#: templates/questions.html:176
#, python-format
msgid ""
"\n"
@@ -1947,16 +2313,10 @@ msgid_plural ""
"\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:123
-#, fuzzy, python-format
+#: templates/questions.html:182
+#, python-format
msgid ""
"\n"
"\t\t\t\thave total %(q_num)s questions\n"
@@ -1965,303 +2325,458 @@ 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'"
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/questions.html:191
+#, python-format
+msgid ""
+"\n"
+" have total %(q_num)s questions tagged %(tagname)s\n"
+" "
+msgid_plural ""
+"\n"
+" have total %(q_num)s questions tagged %(tagname)s\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/questions.html:199
+#, python-format
+msgid ""
+"\n"
+" have total %(q_num)s questions containing %(searchtitle)"
+"s in full text\n"
+" "
+msgid_plural ""
+"\n"
+" have total %(q_num)s questions containing %(searchtitle)"
+"s in full text\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/questions.html:205
+#, python-format
+msgid ""
+"\n"
+" have total %(q_num)s questions containing %(searchtitle)"
+"s\n"
+" "
+msgid_plural ""
+"\n"
+" have total %(q_num)s questions containing %(searchtitle)"
+"s\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/questions.html:213
+#, python-format
+msgid ""
+"\n"
+" have total %(q_num)s unanswered questions\n"
+" "
+msgid_plural ""
+"\n"
+" have total %(q_num)s unanswered questions\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/questions.html:132
+#: templates/questions.html:219
+#, python-format
+msgid ""
+"\n"
+" have total %(q_num)s questions\n"
+" "
+msgid_plural ""
+"\n"
+" have total %(q_num)s questions\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/questions.html:230
msgid "latest questions info"
-msgstr "<strong>Más recientes</strong> preguntas son mostradas primero."
+msgstr ""
-#: templates/questions.html:136
+#: templates/questions.html:234
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:137
+#: templates/questions.html:235
msgid "Most recently answered ones are shown first."
-msgstr "Las más recientemente respondidas son mostradas primero."
+msgstr ""
-#: templates/questions.html:141
+#: templates/questions.html:239
msgid "Questions sorted by <strong>number of responses</strong>."
-msgstr "Preguntas ordenadas por <strong>número de respuestas</strong>."
+msgstr ""
-#: templates/questions.html:142
+#: templates/questions.html:240
msgid "Most answered questions are shown first."
-msgstr "Preguntas más respondidas aparecen primero."
+msgstr ""
-#: templates/questions.html:146
+#: templates/questions.html:244
msgid "Questions are sorted by the <strong>number of votes</strong>."
-msgstr "Las preguntas son ordenadas por el <strong>número de votos</strong>."
+msgstr ""
-#: templates/questions.html:147
+#: templates/questions.html:245
msgid "Most voted questions are shown first."
-msgstr "Las preguntas más votadas son mostradas primero."
+msgstr ""
-#: templates/questions.html:154 templates/unanswered.html:102
+#: templates/questions.html:253 templates/unanswered.html:118
msgid "Related tags"
-msgstr "Etiquetas relacionadas"
+msgstr ""
+
+#: templates/questions.html:263 templates/tag_selector.html:10
+#: templates/tag_selector.html.py:27
+#, python-format
+msgid "see questions tagged '%(tag_name)s'"
+msgstr ""
#: templates/reopen.html:6 templates/reopen.html.py:16
msgid "Reopen question"
-msgstr "Re-abrir pregunta"
+msgstr ""
#: templates/reopen.html:19
msgid "Open the previously closed question"
-msgstr "Abrir pregunta previamente cerrada"
+msgstr ""
#: templates/reopen.html:22
msgid "The question was closed for the following reason "
-msgstr "La pregunta fue cerrada por el siguiente motivo "
+msgstr ""
#: templates/reopen.html:22
msgid "reason - leave blank in english"
-msgstr "razón - "
+msgstr ""
#: templates/reopen.html:22
msgid "on "
-msgstr "el "
+msgstr ""
#: templates/reopen.html:22
msgid "date closed"
-msgstr "fecha cerrada"
+msgstr ""
#: templates/reopen.html:29
msgid "Reopen this question"
-msgstr "Re-abrir esta pregunta"
+msgstr ""
-#: templates/revisions_answer.html:7 templates/revisions_answer.html.py:36
-#: templates/revisions_question.html:8 templates/revisions_question.html:36
+#: templates/revisions_answer.html:7 templates/revisions_answer.html.py:38
+#: templates/revisions_question.html:8 templates/revisions_question.html:38
msgid "Revision history"
-msgstr "Historial de revisiones"
+msgstr ""
+
+#: templates/revisions_answer.html:50 templates/revisions_question.html:50
+msgid "click to hide/show revision"
+msgstr ""
+
+#: templates/tag_selector.html:4
+msgid "Interesting tags"
+msgstr ""
+
+#: templates/tag_selector.html:14
+#, python-format
+msgid "remove '%(tag_name)s' from the list of interesting tags"
+msgstr ""
+
+#: templates/tag_selector.html:20 templates/tag_selector.html.py:37
+msgid "Add"
+msgstr ""
+
+#: templates/tag_selector.html:21
+msgid "Ignored tags"
+msgstr ""
+
+#: templates/tag_selector.html:31
+#, python-format
+msgid "remove '%(tag_name)s' from the list of ignored tags"
+msgstr ""
+
+#: templates/tag_selector.html:40
+msgid "keep ingored questions hidden"
+msgstr ""
#: templates/tags.html:6 templates/tags.html.py:30
msgid "Tag list"
-msgstr "Lista de etiquetas"
+msgstr ""
#: templates/tags.html:32
msgid "sorted alphabetically"
-msgstr "ordenar alfabéticamente"
+msgstr ""
#: templates/tags.html:32
msgid "by name"
-msgstr "por nombre"
+msgstr ""
#: templates/tags.html:33
msgid "sorted by frequency of tag use"
-msgstr "ordenar por frecuencia de uso de la etiqueta"
+msgstr ""
#: templates/tags.html:33
msgid "by popularity"
-msgstr "por popularidad"
+msgstr ""
#: templates/tags.html:39
msgid "All tags matching query"
-msgstr "Todas las etiquetas que coincidan con la busqueda"
+msgstr ""
#: templates/tags.html:39
msgid "all tags - make this empty in english"
-msgstr "todas las tags"
+msgstr ""
-#: templates/unanswered.html:7 templates/unanswered.html.py:18
-msgid "Unanswered questions"
-msgstr "Preguntas sin respuesta"
+#: templates/tags.html:42
+msgid "Nothing found"
+msgstr ""
-#: templates/unanswered.html:97
+#: templates/unanswered.html:114
#, python-format
msgid "have %(num_q)s unanswered questions"
msgstr ""
-"<div class=\"questions-count\">%(num_q)s</div> preguntas <strong>sin "
-"respuesta</strong> "
#: templates/user_edit.html:6
msgid "Edit user profile"
-msgstr "Editar perfil de usuario"
+msgstr ""
#: templates/user_edit.html:19
msgid "edit profile"
-msgstr "editar perfil"
+msgstr ""
#: templates/user_edit.html:31
msgid "image associated with your email address"
-msgstr "imagen asociada con tu email"
+msgstr ""
#: templates/user_edit.html:31
-msgid "avatar"
-msgstr "avatar"
+#, python-format
+msgid "avatar, see %(gravatar_faq_url)s"
+msgstr ""
-#: templates/user_edit.html:36 templates/user_info.html:31
+#: templates/user_edit.html:36 templates/user_info.html:56
msgid "Registered user"
-msgstr "Usuario registrado"
+msgstr ""
-#: templates/user_edit.html:82
+#: templates/user_edit.html:86 templates/user_email_subscriptions.html:23
msgid "Update"
-msgstr "Actualización"
+msgstr ""
+
+#: templates/user_email_subscriptions.html:8
+msgid "Email subscription settings"
+msgstr ""
+
+#: templates/user_email_subscriptions.html:9
+msgid "email subscription settings info"
+msgstr ""
+
+#: templates/user_email_subscriptions.html:24
+msgid "Stop sending email"
+msgstr ""
+
+#: templates/user_info.html:22
+msgid "karma"
+msgstr ""
+
+#: templates/user_info.html:32
+msgid "Moderate this user"
+msgstr ""
-#: templates/user_info.html:34
+#: templates/user_info.html:45
msgid "update profile"
-msgstr "actualizar perfil de usuario"
+msgstr ""
-#: templates/user_info.html:40
+#: templates/user_info.html:60
msgid "real name"
-msgstr "nombre real"
+msgstr ""
-#: templates/user_info.html:45
+#: templates/user_info.html:65
msgid "member for"
-msgstr "miembro de"
+msgstr ""
-#: templates/user_info.html:50
+#: templates/user_info.html:70
msgid "last seen"
-msgstr "última vez visto"
+msgstr ""
-#: templates/user_info.html:56
+#: templates/user_info.html:76
msgid "user website"
-msgstr "sitio web del usuario"
+msgstr ""
-#: templates/user_info.html:62
+#: templates/user_info.html:82
msgid "location"
-msgstr "ubicación"
+msgstr ""
-#: templates/user_info.html:69
+#: templates/user_info.html:89
msgid "age"
-msgstr "edad"
+msgstr ""
-#: templates/user_info.html:70
+#: templates/user_info.html:90
msgid "age unit"
-msgstr "unidad de edad"
+msgstr ""
-#: templates/user_info.html:76
+#: templates/user_info.html:96
msgid "todays unused votes"
-msgstr "votos de hoy no usados"
+msgstr ""
-#: templates/user_info.html:77
+#: templates/user_info.html:97
msgid "votes left"
-msgstr "votos restantes"
-
-#: templates/user_preferences.html:10
-msgid "Connect with Twitter"
-msgstr "Conectar con Twitter"
-
-#: templates/user_preferences.html:13
-msgid "Twitter account name:"
-msgstr "Nombre de usuario en Twitter:"
-
-#: templates/user_preferences.html:15
-msgid "Twitter password:"
-msgstr "Contraseña de Twitter:"
-
-#: templates/user_preferences.html:17
-msgid "Send my Questions to Twitter"
-msgstr "Enviar mis preguntas a Twitter"
-
-#: templates/user_preferences.html:18
-msgid "Send my Answers to Twitter"
-msgstr "Enviar mis respuestas a Twitter"
+msgstr ""
-#: templates/user_preferences.html:19
-msgid "Save"
-msgstr "Guardar"
+#: templates/user_stats.html:12
+#, python-format
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Question\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(counter)s</span> Questions\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/user_stats.html:10
-msgid "User questions"
-msgstr "Preguntas del usuario"
+#: templates/user_stats.html:23
+#, python-format
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Answer\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(counter)s</span> Answers\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/user_stats.html:20
+#: templates/user_stats.html:36
#, python-format
msgid "the answer has been voted for %(vote_count)s times"
-msgstr "la respuesta ha sido votada %(vote_count)s veces"
+msgstr ""
-#: templates/user_stats.html:20
+#: templates/user_stats.html:36
msgid "this answer has been selected as correct"
-msgstr "esta respuesta ha sido seleccionada como correcta"
+msgstr ""
-#: templates/user_stats.html:28
+#: templates/user_stats.html:46
#, python-format
-msgid "the answer has been commented %(comment_count)s times"
-msgstr "la respuesta ha sido comentada %(comment_count)s veces"
+msgid ""
+"\n"
+" (one comment)\n"
+" "
+msgid_plural ""
+"\n"
+" the answer has been commented %(comment_count)s times\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/user_stats.html:36
-#, fuzzy
-msgid "Votes"
-msgstr "votos"
+#: templates/user_stats.html:61
+#, python-format
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Vote\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(cnt)s</span> Votes\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/user_stats.html:41
+#: templates/user_stats.html:72
msgid "thumb up"
msgstr ""
-#: templates/user_stats.html:42
+#: templates/user_stats.html:73
msgid "user has voted up this many times"
-msgstr "el usuario ha votado positivo esta cantidad de veces"
+msgstr ""
-#: templates/user_stats.html:46
+#: templates/user_stats.html:77
msgid "thumb down"
msgstr ""
-#: templates/user_stats.html:47
+#: templates/user_stats.html:78
msgid "user voted down this many times"
-msgstr "el usuario voto negativo esta cantidad de veces"
+msgstr ""
-#: templates/user_stats.html:54
-msgid "Tags"
-msgstr "Etiquetas"
+#: templates/user_stats.html:87
+#, python-format
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Tag\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(counter)s</span> Tags\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
-#: templates/user_stats.html:61
+#: templates/user_stats.html:100
+#, python-format
+msgid ""
+"see other questions with %(view_user)s's contributions tagged '%(tag_name)s' "
+msgstr ""
+
+#: templates/user_stats.html:115
#, python-format
-msgid "see other questions tagged '%(tag)s' "
-msgstr "ver otras preguntas etiqueteadas '%(tag)s'"
+msgid ""
+"\n"
+" <span class=\"count\">1</span> Badge\n"
+" "
+msgid_plural ""
+"\n"
+" <span class=\"count\">%(counter)s</span> Badges\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
#: templates/user_tabs.html:7
msgid "User profile"
-msgstr "Perfil de usuario"
+msgstr ""
#: templates/user_tabs.html:16
msgid "graph of user reputation"
-msgstr "gráfica de la reputación del usuario"
+msgstr ""
#: templates/user_tabs.html:17
msgid "reputation history"
-msgstr "historial de reputación"
+msgstr ""
#: templates/user_tabs.html:23
-#, fuzzy
msgid "questions that user selected as his/her favorite"
-msgstr "esta pregunta ha sido seleccionada como favorita"
+msgstr ""
#: templates/user_tabs.html:24
msgid "favorites"
-msgstr "favoritos"
-
-#: templates/user_tabs.html:28
-msgid "settings"
-msgstr "preferencias"
+msgstr ""
#: templates/users.html:6 templates/users.html.py:24
msgid "Users"
-msgstr "Usuarios"
+msgstr ""
#: templates/users.html:27
msgid "recent"
-msgstr "reciente"
+msgstr ""
#: templates/users.html:28
msgid "oldest"
-msgstr "más viejo"
+msgstr ""
#: templates/users.html:29
msgid "by username"
-msgstr "por nombre de usuario"
+msgstr ""
#: templates/users.html:35
#, python-format
msgid "users matching query %(suser)s:"
-msgstr "usuarios que coincidan con la busqueda %(suser)s:"
+msgstr ""
#: templates/users.html:39
msgid "Nothing found."
-msgstr "Nada encontrado."
+msgstr ""
#: templates/users_questions.html:11
msgid "this questions was selected as favorite"
-msgstr "esta pregunta ha sido seleccionada como favorita"
+msgstr ""
#: templates/users_questions.html:12
msgid "thumb-up on"
@@ -2271,276 +2786,289 @@ msgstr ""
msgid "thumb-up off"
msgstr ""
-#: templates/users_questions.html:35
+#: templates/users_questions.html:34
msgid "this answer has been accepted to be correct"
-msgstr "esta respuesta ha sido aceptada como correcta"
+msgstr ""
-#: templates/authopenid/changeemail.html:7
-#: templates/authopenid/changeemail.html:33
+#: templates/authopenid/changeemail.html:3
+#: templates/authopenid/changeemail.html:9
+#: templates/authopenid/changeemail.html:38
msgid "Change email"
-msgstr "Cambiar dirección email"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:11
+msgid "Save your email address"
+msgstr ""
-#: templates/authopenid/changeemail.html:10
+#: templates/authopenid/changeemail.html:16
#, python-format
msgid "change %(email)s info"
-msgstr "Cambiar información del correo electrónico %(email)s"
+msgstr ""
-#: 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:18
+#, python-format
+msgid "here is why email is required, see %(gravatar_faq_url)s"
+msgstr ""
-#: templates/authopenid/changeemail.html:30
+#: templates/authopenid/changeemail.html:31
msgid "Your new Email"
-msgstr "Tu nuevo Email"
+msgstr ""
#: templates/authopenid/changeemail.html:31
-#: templates/authopenid/signin.html:143
-msgid "Password"
-msgstr "Contraseña"
+msgid "Your Email"
+msgstr ""
-#: templates/authopenid/changeemail.html:42
-#, fuzzy
+#: templates/authopenid/changeemail.html:38
+msgid "Save Email"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:49
msgid "Validate email"
-msgstr "Cambiar dirección email"
+msgstr ""
-#: templates/authopenid/changeemail.html:45
+#: templates/authopenid/changeemail.html:52
#, python-format
-msgid "validate %(email)s info"
-msgstr "validar información de %(email)s "
+msgid "validate %(email)s info or go to %(change_email_url)s"
+msgstr ""
-#: templates/authopenid/changeemail.html:50
+#: templates/authopenid/changeemail.html:57
msgid "Email not changed"
-msgstr "Email no modificado."
+msgstr ""
-#: templates/authopenid/changeemail.html:53
+#: templates/authopenid/changeemail.html:60
#, python-format
-msgid "old %(email)s kept"
-msgstr "se ha conservado el viejo email %(email)s "
+msgid "old %(email)s kept, if you like go to %(change_email_url)s"
+msgstr ""
-#: templates/authopenid/changeemail.html:58
+#: templates/authopenid/changeemail.html:65
msgid "Email changed"
-msgstr "Email modificado."
+msgstr ""
-#: templates/authopenid/changeemail.html:61
+#: templates/authopenid/changeemail.html:68
#, python-format
msgid "your current %(email)s can be used for this"
-msgstr "tu email actual %(email)s puede ser usado para esto"
+msgstr ""
-#: templates/authopenid/changeemail.html:66
+#: templates/authopenid/changeemail.html:73
msgid "Email verified"
-msgstr "Email verificado"
+msgstr ""
-#: templates/authopenid/changeemail.html:69
+#: templates/authopenid/changeemail.html:76
msgid "thanks for verifying email"
-msgstr "gracias por verificar su correo"
+msgstr ""
-#: templates/authopenid/changeemail.html:74
+#: templates/authopenid/changeemail.html:81
msgid "email key not sent"
-msgstr "llave de correo no enviada"
+msgstr ""
-#: templates/authopenid/changeemail.html:77
+#: templates/authopenid/changeemail.html:84
#, 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"
+msgstr ""
+
+#: templates/authopenid/changeopenid.html:4
+#: templates/authopenid/changeopenid.html:30
+#: templates/authopenid/settings.html:34
+msgid "Change OpenID"
+msgstr ""
#: templates/authopenid/changeopenid.html:8
msgid "Account: change OpenID URL"
-msgstr "Cuenta: cambiar la URL de OpenID"
+msgstr ""
#: 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!"
+msgstr ""
+
+#: templates/authopenid/changeopenid.html:14
+#: templates/authopenid/delete.html:14 templates/authopenid/delete.html:24
+msgid "Please correct errors below:"
+msgstr ""
#: templates/authopenid/changeopenid.html:29
msgid "OpenID URL:"
-msgstr "URL de OpenID:"
+msgstr ""
-#: templates/authopenid/changeopenid.html:30
-#: templates/authopenid/settings.html:34
-msgid "Change OpenID"
-msgstr "Cambiar OpenID"
+#: templates/authopenid/changepw.html:5 templates/authopenid/changepw.html:14
+#: templates/authopenid/settings.html:29
+msgid "Change password"
+msgstr ""
-#: templates/authopenid/changepw.html:14
+#: templates/authopenid/changepw.html:7
msgid "Account: change password"
-msgstr "Cuenta: cambiar contraseña"
+msgstr ""
-#: templates/authopenid/changepw.html:17
+#: templates/authopenid/changepw.html:8
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:27
-msgid "Current password"
-msgstr "Contraseña actual"
-
-#: templates/authopenid/changepw.html:28
-msgid "New password"
-msgstr "Nueva contraseña"
-
-#: templates/authopenid/changepw.html:29
-msgid "New password again"
-msgstr "Nueva contraseña nuevamente"
-
-#: templates/authopenid/changepw.html:30 templates/authopenid/settings.html:29
-msgid "Change password"
-msgstr "Cambiar contraseña"
+msgstr ""
-#: templates/authopenid/complete.html:5
+#: templates/authopenid/complete.html:19
msgid "Connect your OpenID with this site"
-msgstr "Vincular tu OpenID con este sitio"
+msgstr ""
-#: templates/authopenid/complete.html:8
+#: templates/authopenid/complete.html:22
msgid "Connect your OpenID with your account on this site"
-msgstr "Vincular tu OpenID con tu cuenta en este sitio"
+msgstr ""
+
+#: templates/authopenid/complete.html:27
+#, python-format
+msgid "register new %(provider)s account info, see %(gravatar_faq_url)s"
+msgstr ""
+
+#: templates/authopenid/complete.html:31
+#, python-format
+msgid ""
+"%(username)s already exists, choose another name for \n"
+" %(provider)s. Email is required too, see %"
+"(gravatar_faq_url)s\n"
+" "
+msgstr ""
-#: templates/authopenid/complete.html:12
+#: templates/authopenid/complete.html:35
#, python-format
-msgid "register new %(provider)s account info"
-msgstr "Registrar una nueva cuenta %(provider)s."
+msgid ""
+"register new external %(provider)s account info, see %(gravatar_faq_url)s"
+msgstr ""
-#: templates/authopenid/complete.html:14
+#: templates/authopenid/complete.html:40
msgid "This account already exists, please use another."
-msgstr "Esta cuenta ya existe, por favor usar otra."
+msgstr ""
-#: templates/authopenid/complete.html:19 templates/authopenid/complete.html:32
-#: templates/authopenid/sendpw.html:16 templates/authopenid/signin.html:130
+#: templates/authopenid/complete.html:55
msgid "Sorry, looks like we have some errors:"
-msgstr "Ups, parece que hay errores:"
+msgstr ""
-#: templates/authopenid/complete.html:47
+#: templates/authopenid/complete.html:76
msgid "Screen name label"
-msgstr "Nombre de Usuario"
+msgstr ""
-#: templates/authopenid/complete.html:48
+#: templates/authopenid/complete.html:83
msgid "Email address label"
-msgstr "Su email (correo electrónico)"
+msgstr ""
-#: templates/authopenid/complete.html:49
+#: templates/authopenid/complete.html:89 templates/authopenid/signup.html:15
+msgid "receive updates motivational blurb"
+msgstr ""
+
+#: templates/authopenid/complete.html:93
+msgid "Tag filter tool will be your right panel, once you log in."
+msgstr ""
+
+#: templates/authopenid/complete.html:95
msgid "create account"
-msgstr "crear cuenta"
+msgstr ""
-#: templates/authopenid/complete.html:56
+#: templates/authopenid/complete.html:104
msgid "Existing account"
-msgstr "Cuenta existente"
+msgstr ""
-#: templates/authopenid/complete.html:57
+#: templates/authopenid/complete.html:105
msgid "user name"
-msgstr "nombre de usuario"
+msgstr ""
-#: templates/authopenid/complete.html:58
+#: templates/authopenid/complete.html:106
msgid "password"
-msgstr "contraseña"
+msgstr ""
-#: templates/authopenid/complete.html:61
+#: templates/authopenid/complete.html:111
msgid "Register"
-msgstr "Registrarse"
+msgstr ""
-#: templates/authopenid/complete.html:62
+#: templates/authopenid/complete.html:112 templates/authopenid/signin.html:140
msgid "Forgot your password?"
-msgstr "¿Olvidaste tu contraseña?"
+msgstr ""
-#: templates/authopenid/delete.html:9
+#: templates/authopenid/delete.html:4 templates/authopenid/settings.html:38
+msgid "Delete account"
+msgstr ""
+
+#: templates/authopenid/delete.html:8
msgid "Account: delete account"
-msgstr "Cuenta: borrar cuenta"
+msgstr ""
-#: templates/authopenid/delete.html:13
+#: templates/authopenid/delete.html:12
msgid ""
"Note: After deleting your account, anyone will be able to register this "
"username."
msgstr ""
-"Nota: Luego de borrar tu cuenta, cualquiera va a poder registrarse con este "
-"nombre de usuario."
-#: templates/authopenid/delete.html:17
+#: templates/authopenid/delete.html:16
msgid "Check confirm box, if you want delete your account."
-msgstr "Marca caja de confirmación, si deseas borrar tu cuenta."
+msgstr ""
-#: templates/authopenid/delete.html:20
+#: templates/authopenid/delete.html:19
msgid "Password:"
-msgstr "Contraseña"
+msgstr ""
-#: templates/authopenid/delete.html:32
+#: templates/authopenid/delete.html:31
msgid "I am sure I want to delete my account."
-msgstr "Estoy seguro que quiero borrar mi cuenta."
+msgstr ""
-#: templates/authopenid/delete.html:33
+#: templates/authopenid/delete.html:32
msgid "Password/OpenID URL"
-msgstr "Contraseña/OpenID URL"
+msgstr ""
-#: templates/authopenid/delete.html:33
+#: templates/authopenid/delete.html:32
msgid "(required for your security)"
-msgstr "(requerido por tu seguridad)"
+msgstr ""
-#: templates/authopenid/delete.html:35
+#: templates/authopenid/delete.html:34
msgid "Delete account permanently"
-msgstr "Borrar la cuenta de forma permanente"
+msgstr ""
-#: templates/authopenid/sendpw.html:4 templates/authopenid/sendpw.html.py:8
-msgid "Send new password"
-msgstr "Enviar nueva contraseña"
+#: templates/authopenid/external_legacy_login_info.html:4
+#: templates/authopenid/external_legacy_login_info.html:7
+msgid "Traditional login information"
+msgstr ""
-#: 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/external_legacy_login_info.html:17
+msgid "how to login with password through external login website"
+msgstr ""
-#: templates/authopenid/sendpw.html:13
-msgid ""
-"Please enter your username below and new password will be sent to your "
-"registered e-mail"
+#: templates/authopenid/sendpw.html:4 templates/authopenid/sendpw.html.py:7
+msgid "Send new password"
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:28
-msgid "User name"
-msgstr "Nombre de usuario"
+#: templates/authopenid/sendpw.html:10
+msgid "password recovery information"
+msgstr ""
-#: templates/authopenid/sendpw.html:30
+#: templates/authopenid/sendpw.html:21
msgid "Reset password"
-msgstr "Re-crear contraseña"
+msgstr ""
-#: templates/authopenid/sendpw.html:30
+#: templates/authopenid/sendpw.html:22
msgid "return to login"
-msgstr "volver a 'Ingresar'"
+msgstr ""
-#: templates/authopenid/sendpw.html:33
-msgid ""
-"Note: your new password will be activated only after you click the "
-"activation link in the email message"
+#: templates/authopenid/settings.html:4
+msgid "Account functions"
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:30
msgid "Give your account a new password."
-msgstr "Crea una nueva contraseña para tu cuenta."
+msgstr ""
#: templates/authopenid/settings.html:31
msgid "Change email "
-msgstr "Cambiar email "
+msgstr ""
#: 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."
+msgstr ""
#: templates/authopenid/settings.html:35
msgid "Change openid associated to your account"
-msgstr "Cambia el OpenID asociado a tu cuenta"
-
-#: templates/authopenid/settings.html:38
-msgid "Delete account"
-msgstr "Eliminar cuenta"
+msgstr ""
#: 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"
+msgstr ""
-#: templates/authopenid/signin.html:4 templates/authopenid/signin.html:21
+#: templates/authopenid/signin.html:5 templates/authopenid/signin.html:21
msgid "User login"
-msgstr "Ingreso de usuario"
+msgstr ""
#: templates/authopenid/signin.html:28
#, python-format
@@ -2550,10 +3078,6 @@ msgid ""
"log in\n"
" "
msgstr ""
-"\n"
-" Tu respuesta a %(title)s %(summary)s será publicada una vez "
-"que ingreses \n"
-" "
#: templates/authopenid/signin.html:35
#, python-format
@@ -2562,150 +3086,85 @@ msgid ""
" %(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:42
msgid "Click to sign in through any of these services."
-msgstr "Clickea para entrar por cualquiera de estos servicios."
+msgstr ""
-#: templates/authopenid/signin.html:115
+#: templates/authopenid/signin.html:117
msgid "Enter your <span id=\"enter_your_what\">Provider user name</span>"
-msgstr "Ingresa tu <span id=\"enter_your_what\">nombre de usuario</span>"
+msgstr ""
-#: templates/authopenid/signin.html:122
+#: templates/authopenid/signin.html:124
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:124 templates/authopenid/signin.html:146
+#: templates/authopenid/signin.html:126 templates/authopenid/signin.html:138
msgid "Login"
-msgstr "Ingresar"
+msgstr ""
-#: templates/authopenid/signin.html:127
+#: templates/authopenid/signin.html:129
msgid "Enter your login name and password"
-msgstr "Introdusca su nombre de usuario y contraseña"
+msgstr ""
-#: templates/authopenid/signin.html:141
+#: templates/authopenid/signin.html:133
msgid "Login name"
-msgstr "Nombre de usuario"
+msgstr ""
-#: templates/authopenid/signin.html:147
-msgid "Create account"
-msgstr "Crear cuenta"
+#: templates/authopenid/signin.html:135
+msgid "Password"
+msgstr ""
-#: templates/authopenid/signin.html:148
-msgid "I forgot my password"
-msgstr "¿Olvidaste tu contraseña?"
+#: templates/authopenid/signin.html:139
+msgid "Create account"
+msgstr ""
-#: templates/authopenid/signin.html:157
+#: templates/authopenid/signin.html:149
msgid "Why use OpenID?"
-msgstr "¿Porqué usar OpenID?"
+msgstr ""
-#: templates/authopenid/signin.html:160
+#: templates/authopenid/signin.html:152
msgid "with openid it is easier"
-msgstr "Con OpenID no necesitas crear un nuevo nombre de usuario y contraseña."
+msgstr ""
-#: templates/authopenid/signin.html:163
+#: templates/authopenid/signin.html:155
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:166
+#: templates/authopenid/signin.html:158
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:169
+#: templates/authopenid/signin.html:161
msgid "openid is supported open standard"
msgstr ""
-"OpenID es basado en un standard abierto, apoyado por muchas organizaciones."
-#: templates/authopenid/signin.html:174
+#: templates/authopenid/signin.html:166
msgid "Find out more"
-msgstr "Averigua más"
+msgstr ""
-#: templates/authopenid/signin.html:175
+#: templates/authopenid/signin.html:167
msgid "Get OpenID"
-msgstr "Adquiere una OpenID"
+msgstr ""
-#: templates/authopenid/signup.html:4 templates/authopenid/signup.html.py:8
+#: templates/authopenid/signup.html:4
msgid "Signup"
-msgstr "Registrate"
+msgstr ""
-#: templates/authopenid/signup.html:12
-msgid ""
-"We support two types of user registration: conventional username/password, "
-"and"
+#: templates/authopenid/signup.html:8
+msgid "Create login name and password"
msgstr ""
-"Soportamos dos formas de registro de usuario: convencional usuario/"
-"contraseña, y"
-#: templates/authopenid/signup.html:12
-msgid "the OpenID method"
-msgstr "OpenID"
+#: templates/authopenid/signup.html:10
+msgid "Traditional signup info"
+msgstr ""
#: templates/authopenid/signup.html:17
-msgid "Sorry, looks like we have some errors"
-msgstr "Ups, parece que hay errores."
-
-#: templates/authopenid/signup.html:35
-msgid "Conventional registration"
-msgstr "Registro clásico"
-
-#: templates/authopenid/signup.html:36
-msgid "choose a user name"
-msgstr "elije un nombre de usuario"
-
-#: templates/authopenid/signup.html:42
-msgid "back to login"
-msgstr "volver al ingreso de usuario"
-
-#: templates/authopenid/signup.html:46
-msgid "Register with your OpenID"
-msgstr "Registrate con tu OpenID"
-
-#: templates/authopenid/signup.html:49
-msgid "Login with your OpenID"
-msgstr "Ingresar con tu OpenID"
-
-#~ msgid "register/"
-#~ msgstr "registrarse/"
-
-#~ msgid "we support two login modes"
-#~ msgstr "soportamos dos tipos de ingreso"
-
-#~ msgid "Create new account"
-#~ msgstr "Crear cuenta nueva"
-
-#, fuzzy
-#~ msgid "complete list of quesionts"
-#~ msgstr "lista completa de preguntas"
-
-#~ msgid "votes total"
-#~ msgstr "votos totales"
-
-#~ msgid "Username:"
-#~ msgstr "Nombre de usuario:"
-
-#, fuzzy
-#~ msgid "New password:"
-#~ msgstr "Nueva contraseña:"
-
-#~ msgid "site title"
-#~ msgstr "Preguntalo.com.uy"
-
-#~ msgid "Have a total of"
-#~ msgstr "Hay un total de"
-
-#~ msgid "/account/"
-#~ msgstr "/cuenta/"
+msgid "Create Account"
+msgstr ""
-#~ msgid "content/"
-#~ msgstr "contenido/"
+#: templates/authopenid/signup.html:19
+msgid "return to OpenID login"
+msgstr ""
diff --git a/log/cnprog.log b/middleware/__init__.py~HEAD
index e69de29b..e69de29b 100644
--- a/log/cnprog.log
+++ b/middleware/__init__.py~HEAD
diff --git a/middleware/anon_user.py b/middleware/anon_user.py
new file mode 100644
index 00000000..8422d89b
--- /dev/null
+++ b/middleware/anon_user.py
@@ -0,0 +1,34 @@
+from django.http import HttpResponseRedirect
+from django_authopenid.util import get_next_url
+from django.utils.translation import ugettext as _
+from user_messages import create_message, get_and_delete_messages
+import settings
+import logging
+
+class AnonymousMessageManager(object):
+ def __init__(self,request):
+ self.request = request
+ def create(self,message=''):
+ create_message(self.request,message)
+ def get_and_delete(self):
+ messages = get_and_delete_messages(self.request)
+ return messages
+
+def dummy_deepcopy(*arg):
+ """this is necessary to prevent deepcopy() on anonymous user object
+ that now contains reference to request, which cannot be deepcopied
+ """
+ return None
+
+class ConnectToSessionMessagesMiddleware(object):
+ def process_request(self, request):
+ if not request.user.is_authenticated():
+ request.user.__deepcopy__ = dummy_deepcopy #plug on deepcopy which may be called by django db "driver"
+ request.user.message_set = AnonymousMessageManager(request) #here request is linked to anon user
+ request.user.get_and_delete_messages = request.user.message_set.get_and_delete
+
+ #also set the first greeting one time per session only
+ if 'greeting_set' not in request.session:
+ request.session['greeting_set'] = True
+ msg = _('first time greeting with %(url)s') % {'url':settings.GREETING_URL}
+ request.user.message_set.create(message=msg)
diff --git a/middleware/cancel.py b/middleware/cancel.py
new file mode 100644
index 00000000..f03ff35e
--- /dev/null
+++ b/middleware/cancel.py
@@ -0,0 +1,15 @@
+from django.http import HttpResponseRedirect
+from django_authopenid.util import get_next_url
+import logging
+class CancelActionMiddleware(object):
+ def process_view(self, request, view_func, view_args, view_kwargs):
+ if 'cancel' in request.REQUEST:
+ #todo use session messages for the anonymous users
+ try:
+ msg = getattr(view_func,'CANCEL_MESSAGE')
+ except AttributeError:
+ msg = 'action canceled'
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect(get_next_url(request))
+ else:
+ return None
diff --git a/middleware/pagesize.py b/middleware/pagesize.py
index bb6c7aa3..f6e6fcfd 100644
--- a/middleware/pagesize.py
+++ b/middleware/pagesize.py
@@ -26,4 +26,8 @@ class QuestionsPageSizeMiddleware(object):
user.questions_per_page = pagesize
user.save()
# put pagesize into session
- request.session["pagesize"] = pagesize \ No newline at end of file
+ request.session["pagesize"] = pagesize
+
+ def process_exception(self,request,exception):
+ import logging
+ logging.debug('have exception %s' % str(exception))
diff --git a/migration b/migration
new file mode 100644
index 00000000..eb5dffa1
--- /dev/null
+++ b/migration
@@ -0,0 +1,7 @@
+cp cnprog-current/templates/content/style/style.css test/templates/content/style/
+cp cnprog-current/templates/footer.html test/templates/
+cp cnprog-current/templates/content/images/logo.png test/templates/content/images
+cp cnprog-current/locale/en/LC_MESSAGES/django.po test/locale/en/LC_MESSAGES/
+python manage.py makemessages -l en -e html,py,txt
+#fix fuzzy messages
+python manage.py compilemessages
diff --git a/session_messages/.svn/all-wcprops b/session_messages/.svn/all-wcprops
new file mode 100644
index 00000000..2a15b353
--- /dev/null
+++ b/session_messages/.svn/all-wcprops
@@ -0,0 +1,23 @@
+K 25
+svn:wc:ra_dav:version-url
+V 38
+/svn/!svn/ver/5/trunk/session_messages
+END
+__init__.py
+K 25
+svn:wc:ra_dav:version-url
+V 50
+/svn/!svn/ver/5/trunk/session_messages/__init__.py
+END
+models.py
+K 25
+svn:wc:ra_dav:version-url
+V 48
+/svn/!svn/ver/2/trunk/session_messages/models.py
+END
+context_processors.py
+K 25
+svn:wc:ra_dav:version-url
+V 60
+/svn/!svn/ver/2/trunk/session_messages/context_processors.py
+END
diff --git a/session_messages/.svn/dir-prop-base b/session_messages/.svn/dir-prop-base
new file mode 100644
index 00000000..4cc643b7
--- /dev/null
+++ b/session_messages/.svn/dir-prop-base
@@ -0,0 +1,6 @@
+K 10
+svn:ignore
+V 6
+*.pyc
+
+END
diff --git a/session_messages/.svn/entries b/session_messages/.svn/entries
new file mode 100644
index 00000000..67c0db8a
--- /dev/null
+++ b/session_messages/.svn/entries
@@ -0,0 +1,64 @@
+8
+
+dir
+5
+http://django-session-messages.googlecode.com/svn/trunk/session_messages
+http://django-session-messages.googlecode.com/svn
+
+
+
+2009-03-10T23:30:03.043791Z
+5
+carl.j.meyer
+has-props
+
+svn:special svn:externals svn:needs-lock
+
+
+
+
+
+
+
+
+
+
+
+b8288d2d-7354-0410-af5b-714f73743f4b
+
+__init__.py
+file
+
+
+
+
+2009-10-25T23:36:02.000000Z
+89aa0f71c9973e4889e5fad0b4771a34
+2009-03-10T23:30:03.043791Z
+5
+carl.j.meyer
+
+models.py
+file
+
+
+
+
+2009-10-25T23:36:02.000000Z
+c5b4f274dbb06bc66a14f0c18c9115cd
+2008-08-14T23:13:23.180432Z
+2
+carl.j.meyer
+
+context_processors.py
+file
+
+
+
+
+2009-10-25T23:36:02.000000Z
+24779c7e504d3f7f1918fdf3fe8096bc
+2008-08-14T23:13:23.180432Z
+2
+carl.j.meyer
+
diff --git a/session_messages/.svn/format b/session_messages/.svn/format
new file mode 100644
index 00000000..45a4fb75
--- /dev/null
+++ b/session_messages/.svn/format
@@ -0,0 +1 @@
+8
diff --git a/session_messages/.svn/text-base/__init__.py.svn-base b/session_messages/.svn/text-base/__init__.py.svn-base
new file mode 100644
index 00000000..0136c888
--- /dev/null
+++ b/session_messages/.svn/text-base/__init__.py.svn-base
@@ -0,0 +1,36 @@
+"""
+Lightweight session-based messaging system.
+
+Time-stamp: <2009-03-10 19:22:29 carljm __init__.py>
+
+"""
+VERSION = (0, 1, 'pre')
+
+def create_message (request, message):
+ """
+ Create a message in the current session.
+
+ """
+ assert hasattr(request, 'session'), "django-session-messages requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
+
+ try:
+ request.session['messages'].append(message)
+ except KeyError:
+ request.session['messages'] = [message]
+
+def get_and_delete_messages (request, include_auth=False):
+ """
+ Get and delete all messages for current session.
+
+ Optionally also fetches user messages from django.contrib.auth.
+
+ """
+ assert hasattr(request, 'session'), "django-session-messages requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
+
+ messages = request.session.pop('messages', [])
+
+ if include_auth and request.user.is_authenticated():
+ messages.extend(request.user.get_and_delete_messages())
+
+ return messages
+
diff --git a/session_messages/.svn/text-base/context_processors.py.svn-base b/session_messages/.svn/text-base/context_processors.py.svn-base
new file mode 100644
index 00000000..df9840fd
--- /dev/null
+++ b/session_messages/.svn/text-base/context_processors.py.svn-base
@@ -0,0 +1,48 @@
+"""
+Context processor for lightweight session messages.
+
+Time-stamp: <2008-07-19 23:16:19 carljm context_processors.py>
+
+"""
+from django.utils.encoding import StrAndUnicode
+
+from session_messages import get_and_delete_messages
+
+def session_messages (request):
+ """
+ Returns session messages for the current session.
+
+ """
+ return { 'session_messages': LazyMessages(request) }
+
+class LazyMessages (StrAndUnicode):
+ """
+ Lazy message container, so messages aren't actually retrieved from
+ session and deleted until the template asks for them.
+
+ """
+ def __init__(self, request):
+ self.request = request
+
+ def __iter__(self):
+ return iter(self.messages)
+
+ def __len__(self):
+ return len(self.messages)
+
+ def __nonzero__(self):
+ return bool(self.messages)
+
+ def __unicode__(self):
+ return unicode(self.messages)
+
+ def __getitem__(self, *args, **kwargs):
+ return self.messages.__getitem__(*args, **kwargs)
+
+ def _get_messages(self):
+ if hasattr(self, '_messages'):
+ return self._messages
+ self._messages = get_and_delete_messages(self.request)
+ return self._messages
+ messages = property(_get_messages)
+
diff --git a/session_messages/.svn/text-base/models.py.svn-base b/session_messages/.svn/text-base/models.py.svn-base
new file mode 100644
index 00000000..b67ead6d
--- /dev/null
+++ b/session_messages/.svn/text-base/models.py.svn-base
@@ -0,0 +1,3 @@
+"""
+blank models.py
+"""
diff --git a/session_messages/__init__.py b/session_messages/__init__.py
new file mode 100644
index 00000000..4dd10a6b
--- /dev/null
+++ b/session_messages/__init__.py
@@ -0,0 +1,37 @@
+"""
+Lightweight session-based messaging system.
+
+Time-stamp: <2009-03-10 19:22:29 carljm __init__.py>
+
+"""
+VERSION = (0, 1, 'pre')
+
+def create_message (request, message):
+ """
+ Create a message in the current session.
+
+ """
+ assert hasattr(request, 'session'), "django-session-messages requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
+
+ try:
+ request.session['messages'].append(message)
+ except KeyError:
+ request.session['messages'] = [message]
+
+def get_and_delete_messages (request, include_auth=False):
+ """
+ Get and delete all messages for current session.
+
+ Optionally also fetches user messages from django.contrib.auth.
+
+ """
+ assert hasattr(request, 'session'), "django-session-messages requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
+
+ messages = request.session.pop('messages', [])
+ import logging
+
+ if include_auth and request.user.is_authenticated():
+ messages.extend(request.user.get_and_delete_messages())
+
+ return messages
+
diff --git a/session_messages/context_processors.py b/session_messages/context_processors.py
new file mode 100644
index 00000000..df9840fd
--- /dev/null
+++ b/session_messages/context_processors.py
@@ -0,0 +1,48 @@
+"""
+Context processor for lightweight session messages.
+
+Time-stamp: <2008-07-19 23:16:19 carljm context_processors.py>
+
+"""
+from django.utils.encoding import StrAndUnicode
+
+from session_messages import get_and_delete_messages
+
+def session_messages (request):
+ """
+ Returns session messages for the current session.
+
+ """
+ return { 'session_messages': LazyMessages(request) }
+
+class LazyMessages (StrAndUnicode):
+ """
+ Lazy message container, so messages aren't actually retrieved from
+ session and deleted until the template asks for them.
+
+ """
+ def __init__(self, request):
+ self.request = request
+
+ def __iter__(self):
+ return iter(self.messages)
+
+ def __len__(self):
+ return len(self.messages)
+
+ def __nonzero__(self):
+ return bool(self.messages)
+
+ def __unicode__(self):
+ return unicode(self.messages)
+
+ def __getitem__(self, *args, **kwargs):
+ return self.messages.__getitem__(*args, **kwargs)
+
+ def _get_messages(self):
+ if hasattr(self, '_messages'):
+ return self._messages
+ self._messages = get_and_delete_messages(self.request)
+ return self._messages
+ messages = property(_get_messages)
+
diff --git a/session_messages/models.py b/session_messages/models.py
new file mode 100644
index 00000000..b67ead6d
--- /dev/null
+++ b/session_messages/models.py
@@ -0,0 +1,3 @@
+"""
+blank models.py
+"""
diff --git a/settings.py b/settings.py
index 86b075e4..3bce2879 100644
--- a/settings.py
+++ b/settings.py
@@ -1,28 +1,10 @@
# encoding:utf-8
# Django settings for lanai project.
import os.path
-from django.utils.translation import ugettext as _
-
-#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/'))
-LOGIN_URL = '/%s%s' % (_('account/'), _('signin/'))
-#LOGIN_URL = '/cuenta/ingresar/'
-
-#EMAIL AND ADMINS
-ADMINS = (
- ('CNProg team', 'team@cnprog.com'),
-)
-MANAGERS = ADMINS
-
+import sys
SITE_ID = 1
-ADMIN_MEDIA_PREFIX = '/admin/media/'
+ADMIN_MEDIA_PREFIX = '/forum/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 = (
@@ -39,16 +21,18 @@ MIDDLEWARE_CLASSES = (
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.transaction.TransactionMiddleware',
#'django.middleware.sqlprint.SqlPrintingMiddleware',
+ 'middleware.anon_user.ConnectToSessionMessagesMiddleware',
'middleware.pagesize.QuestionsPageSizeMiddleware',
+ 'middleware.cancel.CancelActionMiddleware',
#'debug_toolbar.middleware.DebugToolbarMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
- 'context.auth_processor',
'context.application_settings',
#'django.core.context_processors.i18n',
- 'django.core.context_processors.auth' #this is required for admin
+ 'user_messages.context_processors.user_messages',#must be before auth
+ 'django.core.context_processors.auth', #this is required for admin
)
ROOT_URLCONF = 'urls'
@@ -74,9 +58,12 @@ INSTALLED_APPS = (
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.humanize',
+ 'django.contrib.sitemaps',
'forum',
'django_authopenid',
+ 'djangosphinx',
#'debug_toolbar' ,
+ 'user_messages',
)
import django
DJANGO_VERSION = django.get_version()
diff --git a/settings_local.py.dist b/settings_local.py.dist
index 003c7491..136d4bdd 100644
--- a/settings_local.py.dist
+++ b/settings_local.py.dist
@@ -1,17 +1,39 @@
# encoding:utf-8
import os.path
+<<<<<<< HEAD:settings_local.py.dist
+=======
+from django.utils.translation import ugettext as _
+
+>>>>>>> evgenyfadeev/master:settings_local.py.dist
SITE_SRC_ROOT = os.path.dirname(__file__)
LOG_FILENAME = 'django.lanai.log'
#for logging
import logging
logging.basicConfig(filename=os.path.join(SITE_SRC_ROOT, 'log', LOG_FILENAME), level=logging.DEBUG,)
+<<<<<<< HEAD:settings_local.py.dist
DATABASE_NAME = '' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_ENGINE = '' #mysql, etc
<<<<<<< HEAD:settings_local.py.dist
+=======
+
+#ADMINS and MANAGERS
+ADMINS = (('Forum Admin', 'forum@example.com'),)
+MANAGERS = ADMINS
+
+#DEBUG SETTINGS
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+INTERNAL_IPS = ('127.0.0.1',)
+
+DATABASE_NAME = 'cnprog' # Or path to database file if using sqlite3.
+DATABASE_USER = '' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_ENGINE = 'mysql' #mysql, etc
+>>>>>>> evgenyfadeev/master:settings_local.py.dist
#Moved from settings.py for better organization. (please check it up to clean up settings.py)
@@ -22,18 +44,33 @@ DATABASE_ENGINE = '' #mysql, etc
>>>>>>> 6214863f362fd0702af79abaade0de6736d12e96:settings_local.py.dist
#email server settings
SERVER_EMAIL = ''
-DEFAULT_FROM_EMAIL = 'team@cnprog.com'
+DEFAULT_FROM_EMAIL = ''
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
+<<<<<<< HEAD:settings_local.py.dist
EMAIL_SUBJECT_PREFIX = '[cnprog.com]'
EMAIL_HOST='smtp.gmail.com'
EMAIL_PORT='587'
EMAIL_USE_TLS=True
<<<<<<< HEAD:settings_local.py.dist
+=======
+EMAIL_SUBJECT_PREFIX = '[CNPROG] '
+EMAIL_HOST='cnprog.com'
+EMAIL_PORT='25'
+EMAIL_USE_TLS=False
+>>>>>>> evgenyfadeev/master:settings_local.py.dist
#LOCALIZATIONS
-TIME_ZONE = 'Asia/Chongqing Asia/Chungking'
-# LANGUAGE_CODE = 'en-us'
+TIME_ZONE = 'America/Tijuana'
+
+###########################
+#
+# this will allow running your forum with url like http://site.com/forum
+#
+# FORUM_SCRIPT_ALIAS = 'forum/'
+#
+FORUM_SCRIPT_ALIAS = '' #no leading slash, default = '' empty string
+
=======
@@ -43,26 +80,58 @@ TIME_ZONE = 'Asia/Chongqing Asia/Chungking'
>>>>>>> 6214863f362fd0702af79abaade0de6736d12e96:settings_local.py.dist
#OTHER SETTINGS
+<<<<<<< HEAD:settings_local.py.dist
APP_TITLE = u'CNProg.com 程序员问答社区'
APP_KEYWORDS = u'技术问答社区,中国程序员,编程技术社区,程序员社区,程序员论坛,程序员wiki,程序员博客'
APP_DESCRIPTION = u'中国程序员的编程技术问答社区。我们做专业的、可协作编辑的技术问答社区。'
APP_INTRO = u' <p>CNProg是一个<strong>面向程序员</strong>的可协作编辑的<strong>开放源代码问答社区</strong>。</p><p> 您可以在这里提问各类<strong>程序技术问题</strong> - 问题不分语言和平台。 同时也希望您对力所能及的问题,给予您的宝贵答案。</p>'
APP_COPYRIGHT = 'Copyright CNPROG.COM 2009'
<<<<<<< HEAD:settings_local.py.dist
+=======
+APP_TITLE = u'CNPROG Q&A Forum'
+APP_KEYWORDS = u'CNPROG,forum,community'
+APP_DESCRIPTION = u'Ask and answer questions.'
+APP_INTRO = u'<p>Ask and answer questions, make the world better!</p>'
+APP_COPYRIGHT = 'Copyright CNPROG, 2009. Some rights reserved under creative commons license.'
+LOGIN_URL = '/%s%s%s' % (FORUM_SCRIPT_ALIAS,'account/','signin/')
+GREETING_URL = LOGIN_URL #may be url of "faq" page or "about", etc
+>>>>>>> evgenyfadeev/master:settings_local.py.dist
=======
>>>>>>> 6214863f362fd0702af79abaade0de6736d12e96:settings_local.py.dist
USE_I18N = True
LANGUAGE_CODE = 'en'
-EMAIL_VALIDATION = 'off'
+EMAIL_VALIDATION = 'off' #string - on|off
MIN_USERNAME_LENGTH = 1
EMAIL_UNIQUE = False
-APP_URL = 'http://server.com' #used by email notif system and RSS
+APP_URL = 'http://cnprog.com' #used by email notif system and RSS
GOOGLE_SITEMAP_CODE = '55uGNnQVJW8p1bbXeF/Xbh9I7nZBM/wLhRz6N/I1kkA='
<<<<<<< HEAD:settings_local.py.dist
GOOGLE_ANALYTICS_KEY = ''
+<<<<<<< HEAD:settings_local.py.dist
BOOKS_ON = True
=======
GOOGLE_ANALYTICS_KEY = ''
>>>>>>> 6214863f362fd0702af79abaade0de6736d12e96:settings_local.py.dist
+=======
+BOOKS_ON = False
+WIKI_ON = True
+USE_EXTERNAL_LEGACY_LOGIN = False
+EXTERNAL_LEGACY_LOGIN_HOST = 'login.cnprog.com'
+EXTERNAL_LEGACY_LOGIN_PORT = 80
+EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = '<span class="orange">CNPROG</span>'
+FEEDBACK_SITE_URL = None #None or url
+
+DJANGO_VERSION = 1.1
+RESOURCE_REVISION=4
+
+USE_SPHINX_SEARCH = True #if True all SPHINX_* settings are required
+#also sphinx search engine and djangosphinxs app must be installed
+#sample sphinx configuration file is /sphinx/sphinx.conf
+SPHINX_API_VERSION = 0x113 #refer to djangosphinx documentation
+SPHINX_SEARCH_INDICES=('cnprog',) #a tuple of index names remember about a comma after the
+#last item, especially if you have just one :)
+SPHINX_SERVER='localhost'
+SPHINX_PORT=3312
+>>>>>>> evgenyfadeev/master:settings_local.py.dist
diff --git a/sphinx/sphinx.conf b/sphinx/sphinx.conf
new file mode 100644
index 00000000..bf4bdc8b
--- /dev/null
+++ b/sphinx/sphinx.conf
@@ -0,0 +1,127 @@
+#if you have many posts, it's best to configure another index for new posts and
+#periodically merge the diff index to the main
+#this is not important until you get to hundreds of thousands posts
+
+source src_cnprog
+{
+ # data source
+ type = mysql
+ sql_host = localhost
+ sql_user = cnprog #replace with your db username
+ sql_pass = secret #replace with your db password
+ sql_db = cnprog #replace with your db name
+ # these two are optional
+ #sql_port = 3306
+ #sql_sock = /var/lib/mysql/mysql.sock
+
+ # pre-query, executed before the main fetch query
+ sql_query_pre = SET NAMES utf8
+
+ # main document fetch query - change the table names if you are using a prefix
+ # this query creates a flat document from each question that includes only latest
+ # revisions of the question and all of it's answers
+ sql_query = SELECT q.id as id, q.title AS title, q.tagnames as tags, qr.text AS text, answers_combined.text AS answers \
+ FROM question AS q \
+ INNER JOIN \
+ ( \
+ SELECT MAX(id) as id, question_id \
+ FROM question_revision \
+ GROUP BY question_id \
+ ) \
+ AS mqr \
+ ON q.id=mqr.question_id \
+ INNER JOIN question_revision AS qr ON qr.id=mqr.id \
+ LEFT JOIN \
+ ( \
+ SELECT GROUP_CONCAT(answer_current.text SEPARATOR '. ') AS text, \
+ question_id \
+ FROM \
+ ( \
+ SELECT a.question_id as question_id, ar.text as text \
+ FROM answer AS a \
+ INNER JOIN \
+ ( \
+ SELECT MAX(id) as id, answer_id \
+ FROM answer_revision \
+ GROUP BY answer_id \
+ ) \
+ AS mar \
+ ON mar.answer_id = a.id \
+ INNER JOIN answer_revision AS ar ON ar.id=mar.id \
+ WHERE a.deleted=0 \
+ ) \
+ AS answer_current \
+ GROUP BY question_id \
+ ) \
+ AS answers_combined ON q.id=answers_combined.question_id \
+ WHERE q.deleted=0;
+
+ # optional - used by command-line search utility to display document information
+ sql_query_info = SELECT title, id FROM question WHERE id=$id
+}
+
+index cnprog {
+ # which document source to index
+ source = src_cnprog
+
+ # this is path and index file name without extension
+ # you may need to change this path or create this folder
+ path = /var/data/sphinx/cnprog_main
+
+ # docinfo (ie. per-document attribute values) storage strategy
+ docinfo = extern
+
+ # morphology
+ morphology = stem_en
+
+ # stopwords file
+ #stopwords = /var/data/sphinx/stopwords.txt
+
+ # minimum word length
+ min_word_len = 1
+
+ # uncomment next 2 lines to allow wildcard (*) searches
+ #min_infix_len = 1
+ #enable_star = 1
+
+ # charset encoding type
+ charset_type = utf-8
+}
+
+# indexer settings
+indexer
+{
+ # memory limit (default is 32M)
+ mem_limit = 64M
+}
+
+# searchd settings
+searchd
+{
+ # IP address on which search daemon will bind and accept
+ # optional, default is to listen on all addresses,
+ # ie. address = 0.0.0.0
+ address = 127.0.0.1
+
+ # port on which search daemon will listen
+ port = 3312
+
+ # searchd run info is logged here - create or change the folder
+ log = /var/log/sphinx/searchd.log
+
+ # all the search queries are logged here
+ query_log = /var/log/sphinx/query.log
+
+ # client read timeout, seconds
+ read_timeout = 5
+
+ # maximum amount of children to fork
+ max_children = 30
+
+ # a file which will contain searchd process ID
+ pid_file = /var/log/sphinx/searchd.pid
+
+ # maximum amount of matches this daemon would ever retrieve
+ # from each index and serve to client
+ max_matches = 1000
+}
diff --git a/sql_scripts/091111_upgrade_evgeny.sql b/sql_scripts/091111_upgrade_evgeny.sql
new file mode 100644
index 00000000..cb76ec3c
--- /dev/null
+++ b/sql_scripts/091111_upgrade_evgeny.sql
@@ -0,0 +1 @@
+ALTER TABLE `auth_user` add column is_approved tinyint(1) not NULL;
diff --git a/sql_scripts/091208_upgrade_evgeny.sql b/sql_scripts/091208_upgrade_evgeny.sql
new file mode 100644
index 00000000..d9c4289a
--- /dev/null
+++ b/sql_scripts/091208_upgrade_evgeny.sql
@@ -0,0 +1 @@
+ALTER TABLE `auth_user` add column hide_ignored_questions tinyint(1) not NULL;
diff --git a/sql_scripts/091208_upgrade_evgeny_1.sql b/sql_scripts/091208_upgrade_evgeny_1.sql
new file mode 100644
index 00000000..b1b4107f
--- /dev/null
+++ b/sql_scripts/091208_upgrade_evgeny_1.sql
@@ -0,0 +1 @@
+ALTER TABLE `auth_user` add column `tag_filter_setting` varchar(16) not NULL default 'ignored';
diff --git a/sql_scripts/update_2009_01_25_001.sql b/sql_scripts/update_2009_01_25_001.sql
index f6f3bed2..16c3487b 100644
--- a/sql_scripts/update_2009_01_25_001.sql
+++ b/sql_scripts/update_2009_01_25_001.sql
@@ -1,2 +1,2 @@
ALTER TABLE `award` ADD `content_type_id` INT NULL
-ALTER TABLE `award` ADD `object_id` INT NULL \ No newline at end of file
+ALTER TABLE `award` ADD `object_id` INT NULL
diff --git a/sql_scripts/update_2009_02_26_001.sql b/sql_scripts/update_2009_02_26_001.sql
index 4113d8f5..a6af5931 100644
--- a/sql_scripts/update_2009_02_26_001.sql
+++ b/sql_scripts/update_2009_02_26_001.sql
@@ -16,4 +16,4 @@ WHERE tagnames like '%c#%'
UPDATE question_revision
SET tagnames = replace(tagnames, 'c#', 'csharp')
-WHERE tagnames like '%c#%' \ No newline at end of file
+WHERE tagnames like '%c#%'
diff --git a/sql_scripts/update_2009_04_10_001.sql b/sql_scripts/update_2009_04_10_001.sql
index e1ceb3bc..8148632a 100644
--- a/sql_scripts/update_2009_04_10_001.sql
+++ b/sql_scripts/update_2009_04_10_001.sql
@@ -1,3 +1,3 @@
ALTER TABLE Tag ADD COLUMN deleted_at datetime default null;
ALTER TABLE Tag ADD COLUMN deleted_by_id INTEGER NULL;
-ALTER TABLE Tag ADD COLUMN deleted TINYINT NOT NULL; \ No newline at end of file
+ALTER TABLE Tag ADD COLUMN deleted TINYINT NOT NULL;
diff --git a/tables.sql b/tables.sql
new file mode 100644
index 00000000..6034c08c
--- /dev/null
+++ b/tables.sql
@@ -0,0 +1,440 @@
+BEGIN;
+CREATE TABLE `forum_emailfeedsetting` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `subscriber_id` integer NOT NULL,
+ `feed_type` varchar(16) NOT NULL,
+ `frequency` varchar(8) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `reported_at` datetime NULL
+)
+;
+ALTER TABLE `forum_emailfeedsetting` ADD CONSTRAINT subscriber_id_refs_id_6fee6730cc813af8 FOREIGN KEY (`subscriber_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `tag` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `name` varchar(255) NOT NULL UNIQUE,
+ `created_by_id` integer NOT NULL,
+ `deleted` bool NOT NULL,
+ `deleted_at` datetime NULL,
+ `deleted_by_id` integer NULL,
+ `used_count` integer UNSIGNED NOT NULL
+)
+;
+ALTER TABLE `tag` ADD CONSTRAINT created_by_id_refs_id_6ae4d97547205d6d FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `tag` ADD CONSTRAINT deleted_by_id_refs_id_6ae4d97547205d6d FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `comment` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `content_type_id` integer NOT NULL,
+ `object_id` integer UNSIGNED NOT NULL,
+ `user_id` integer NOT NULL,
+ `comment` varchar(300) NOT NULL,
+ `added_at` datetime NOT NULL
+)
+;
+ALTER TABLE `comment` ADD CONSTRAINT content_type_id_refs_id_89a4b13ec5a7994 FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
+ALTER TABLE `comment` ADD CONSTRAINT user_id_refs_id_5ba842626be725e8 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `vote` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `content_type_id` integer NOT NULL,
+ `object_id` integer UNSIGNED NOT NULL,
+ `user_id` integer NOT NULL,
+ `vote` smallint NOT NULL,
+ `voted_at` datetime NOT NULL,
+ UNIQUE (`content_type_id`, `object_id`, `user_id`)
+)
+;
+ALTER TABLE `vote` ADD CONSTRAINT content_type_id_refs_id_77dc6ffafedbbec FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
+ALTER TABLE `vote` ADD CONSTRAINT user_id_refs_id_3ce5b20589f5b210 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `flagged_item` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `content_type_id` integer NOT NULL,
+ `object_id` integer UNSIGNED NOT NULL,
+ `user_id` integer NOT NULL,
+ `flagged_at` datetime NOT NULL,
+ UNIQUE (`content_type_id`, `object_id`, `user_id`)
+)
+;
+ALTER TABLE `flagged_item` ADD CONSTRAINT content_type_id_refs_id_261d26c8891bb28c FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
+ALTER TABLE `flagged_item` ADD CONSTRAINT user_id_refs_id_92ae9d35e3c608 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `question` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `title` varchar(300) NOT NULL,
+ `author_id` integer NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` bool NOT NULL,
+ `wikified_at` datetime NULL,
+ `answer_accepted` bool NOT NULL,
+ `closed` bool NOT NULL,
+ `closed_by_id` integer NULL,
+ `closed_at` datetime NULL,
+ `close_reason` smallint NULL,
+ `deleted` bool NOT NULL,
+ `deleted_at` datetime NULL,
+ `deleted_by_id` integer NULL,
+ `locked` bool NOT NULL,
+ `locked_by_id` integer NULL,
+ `locked_at` datetime NULL,
+ `score` integer NOT NULL,
+ `vote_up_count` integer NOT NULL,
+ `vote_down_count` integer NOT NULL,
+ `answer_count` integer UNSIGNED NOT NULL,
+ `comment_count` integer UNSIGNED NOT NULL,
+ `view_count` integer UNSIGNED NOT NULL,
+ `offensive_flag_count` smallint NOT NULL,
+ `favourite_count` integer UNSIGNED NOT NULL,
+ `last_edited_at` datetime NULL,
+ `last_edited_by_id` integer NULL,
+ `last_activity_at` datetime NOT NULL,
+ `last_activity_by_id` integer NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `summary` varchar(180) NOT NULL,
+ `html` longtext NOT NULL
+)
+;
+ALTER TABLE `question` ADD CONSTRAINT author_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `question` ADD CONSTRAINT closed_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`closed_by_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `question` ADD CONSTRAINT deleted_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `question` ADD CONSTRAINT locked_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `question` ADD CONSTRAINT last_edited_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `question` ADD CONSTRAINT last_activity_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`last_activity_by_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `forum_questionview` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `question_id` integer NOT NULL,
+ `who_id` integer NOT NULL,
+ `when` datetime NOT NULL
+)
+;
+ALTER TABLE `forum_questionview` ADD CONSTRAINT question_id_refs_id_fe63ebce6b3cbac FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
+ALTER TABLE `forum_questionview` ADD CONSTRAINT who_id_refs_id_293b67239e957c53 FOREIGN KEY (`who_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `favorite_question` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `question_id` integer NOT NULL,
+ `user_id` integer NOT NULL,
+ `added_at` datetime NOT NULL
+)
+;
+ALTER TABLE `favorite_question` ADD CONSTRAINT question_id_refs_id_2cafd2f21ebe1cc3 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
+ALTER TABLE `favorite_question` ADD CONSTRAINT user_id_refs_id_1632ce11ad7ac7de FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `question_revision` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `question_id` integer NOT NULL,
+ `revision` integer UNSIGNED NOT NULL,
+ `title` varchar(300) NOT NULL,
+ `author_id` integer NOT NULL,
+ `revised_at` datetime NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `summary` varchar(300) NOT NULL,
+ `text` longtext NOT NULL
+)
+;
+ALTER TABLE `question_revision` ADD CONSTRAINT question_id_refs_id_61316ec87bef5296 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
+ALTER TABLE `question_revision` ADD CONSTRAINT author_id_refs_id_79de7cc0b077fdb1 FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `forum_anonymousanswer` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `question_id` integer NOT NULL,
+ `session_key` varchar(40) NOT NULL,
+ `wiki` bool NOT NULL,
+ `added_at` datetime NOT NULL,
+ `ip_addr` char(15) NOT NULL,
+ `author_id` integer NULL,
+ `text` longtext NOT NULL,
+ `summary` varchar(180) NOT NULL
+)
+;
+ALTER TABLE `forum_anonymousanswer` ADD CONSTRAINT question_id_refs_id_17dd6b2f4cc171c7 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
+ALTER TABLE `forum_anonymousanswer` ADD CONSTRAINT author_id_refs_id_3ac41be013fb542e FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `forum_anonymousquestion` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `title` varchar(300) NOT NULL,
+ `session_key` varchar(40) NOT NULL,
+ `text` longtext NOT NULL,
+ `summary` varchar(180) NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `wiki` bool NOT NULL,
+ `added_at` datetime NOT NULL,
+ `ip_addr` char(15) NOT NULL,
+ `author_id` integer NULL
+)
+;
+ALTER TABLE `forum_anonymousquestion` ADD CONSTRAINT author_id_refs_id_2a673297511a98a FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `answer` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `question_id` integer NOT NULL,
+ `author_id` integer NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` bool NOT NULL,
+ `wikified_at` datetime NULL,
+ `accepted` bool NOT NULL,
+ `accepted_at` datetime NULL,
+ `deleted` bool NOT NULL,
+ `deleted_by_id` integer NULL,
+ `locked` bool NOT NULL,
+ `locked_by_id` integer NULL,
+ `locked_at` datetime NULL,
+ `score` integer NOT NULL,
+ `vote_up_count` integer NOT NULL,
+ `vote_down_count` integer NOT NULL,
+ `comment_count` integer UNSIGNED NOT NULL,
+ `offensive_flag_count` smallint NOT NULL,
+ `last_edited_at` datetime NULL,
+ `last_edited_by_id` integer NULL,
+ `html` longtext NOT NULL
+)
+;
+ALTER TABLE `answer` ADD CONSTRAINT question_id_refs_id_2300e0297d6550c9 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
+ALTER TABLE `answer` ADD CONSTRAINT author_id_refs_id_6573e62f192b0170 FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `answer` ADD CONSTRAINT deleted_by_id_refs_id_6573e62f192b0170 FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `answer` ADD CONSTRAINT locked_by_id_refs_id_6573e62f192b0170 FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `answer` ADD CONSTRAINT last_edited_by_id_refs_id_6573e62f192b0170 FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `answer_revision` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `answer_id` integer NOT NULL,
+ `revision` integer UNSIGNED NOT NULL,
+ `author_id` integer NOT NULL,
+ `revised_at` datetime NOT NULL,
+ `summary` varchar(300) NOT NULL,
+ `text` longtext NOT NULL
+)
+;
+ALTER TABLE `answer_revision` ADD CONSTRAINT answer_id_refs_id_47145eaebe77d8fe FOREIGN KEY (`answer_id`) REFERENCES `answer` (`id`);
+ALTER TABLE `answer_revision` ADD CONSTRAINT author_id_refs_id_2c17693c3ccc055f FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `badge` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `name` varchar(50) NOT NULL,
+ `type` smallint NOT NULL,
+ `slug` varchar(50) NOT NULL,
+ `description` varchar(300) NOT NULL,
+ `multiple` bool NOT NULL,
+ `awarded_count` integer UNSIGNED NOT NULL,
+ UNIQUE (`name`, `type`)
+)
+;
+CREATE TABLE `award` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `badge_id` integer NOT NULL,
+ `content_type_id` integer NOT NULL,
+ `object_id` integer UNSIGNED NOT NULL,
+ `awarded_at` datetime NOT NULL,
+ `notified` bool NOT NULL
+)
+;
+ALTER TABLE `award` ADD CONSTRAINT user_id_refs_id_5d197ea32d83e9b6 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `award` ADD CONSTRAINT badge_id_refs_id_4237a025651af0e1 FOREIGN KEY (`badge_id`) REFERENCES `badge` (`id`);
+ALTER TABLE `award` ADD CONSTRAINT content_type_id_refs_id_72f17e2d83bbde26 FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
+CREATE TABLE `repute` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `positive` smallint NOT NULL,
+ `negative` smallint NOT NULL,
+ `question_id` integer NOT NULL,
+ `reputed_at` datetime NOT NULL,
+ `reputation_type` smallint NOT NULL,
+ `reputation` integer NOT NULL
+)
+;
+ALTER TABLE `repute` ADD CONSTRAINT user_id_refs_id_fcf719405a426cd FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `repute` ADD CONSTRAINT question_id_refs_id_4749166abeb39c4e FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
+CREATE TABLE `activity` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `activity_type` smallint NOT NULL,
+ `active_at` datetime NOT NULL,
+ `content_type_id` integer NOT NULL,
+ `object_id` integer UNSIGNED NOT NULL,
+ `is_auditted` bool NOT NULL
+)
+;
+ALTER TABLE `activity` ADD CONSTRAINT user_id_refs_id_6015206347c8583f FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `activity` ADD CONSTRAINT content_type_id_refs_id_78877d15efa8edfd FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
+CREATE TABLE `book` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `short_name` varchar(255) NOT NULL,
+ `author` varchar(255) NOT NULL,
+ `price` numeric(6, 2) NOT NULL,
+ `pages` smallint NOT NULL,
+ `published_at` datetime NOT NULL,
+ `publication` varchar(255) NOT NULL,
+ `cover_img` varchar(255) NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `last_edited_at` datetime NOT NULL
+)
+;
+ALTER TABLE `book` ADD CONSTRAINT user_id_refs_id_607b4cfdf0283c8d FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `book_author_info` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `book_id` integer NOT NULL,
+ `blog_url` varchar(255) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `last_edited_at` datetime NOT NULL
+)
+;
+ALTER TABLE `book_author_info` ADD CONSTRAINT user_id_refs_id_3781e2a5fbe1cfda FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `book_author_info` ADD CONSTRAINT book_id_refs_id_688c8f047c49bbf8 FOREIGN KEY (`book_id`) REFERENCES `book` (`id`);
+CREATE TABLE `book_author_rss` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `book_id` integer NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `url` varchar(255) NOT NULL,
+ `rss_created_at` datetime NOT NULL,
+ `added_at` datetime NOT NULL
+)
+;
+ALTER TABLE `book_author_rss` ADD CONSTRAINT user_id_refs_id_1fd25dcf3596f741 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `book_author_rss` ADD CONSTRAINT book_id_refs_id_f64066171717121 FOREIGN KEY (`book_id`) REFERENCES `book` (`id`);
+CREATE TABLE `forum_anonymousemail` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `key` varchar(32) NOT NULL,
+ `email` varchar(75) NOT NULL UNIQUE,
+ `isvalid` bool NOT NULL
+)
+;
+CREATE TABLE `question_tags` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `question_id` integer NOT NULL,
+ `tag_id` integer NOT NULL,
+ UNIQUE (`question_id`, `tag_id`)
+)
+;
+ALTER TABLE `question_tags` ADD CONSTRAINT question_id_refs_id_35d758e3d99eb83a FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
+ALTER TABLE `question_tags` ADD CONSTRAINT tag_id_refs_id_3b0ddddfbc0346ad FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`);
+CREATE TABLE `question_followed_by` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `question_id` integer NOT NULL,
+ `user_id` integer NOT NULL,
+ UNIQUE (`question_id`, `user_id`)
+)
+;
+ALTER TABLE `question_followed_by` ADD CONSTRAINT question_id_refs_id_6ea9c52125c22aae FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
+ALTER TABLE `question_followed_by` ADD CONSTRAINT user_id_refs_id_49cca2976d30712d FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `book_question` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `book_id` integer NOT NULL,
+ `question_id` integer NOT NULL,
+ UNIQUE (`book_id`, `question_id`)
+)
+;
+ALTER TABLE `book_question` ADD CONSTRAINT book_id_refs_id_535ac8946a43c4d1 FOREIGN KEY (`book_id`) REFERENCES `book` (`id`);
+ALTER TABLE `book_question` ADD CONSTRAINT question_id_refs_id_372b7e81c7aff6d8 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
+CREATE TABLE `django_authopenid_nonce` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `server_url` varchar(255) NOT NULL,
+ `timestamp` integer NOT NULL,
+ `salt` varchar(40) NOT NULL
+)
+;
+CREATE TABLE `django_authopenid_association` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `server_url` longtext NOT NULL,
+ `handle` varchar(255) NOT NULL,
+ `secret` longtext NOT NULL,
+ `issued` integer NOT NULL,
+ `lifetime` integer NOT NULL,
+ `assoc_type` longtext NOT NULL
+)
+;
+CREATE TABLE `django_authopenid_userassociation` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `openid_url` varchar(255) NOT NULL,
+ `user_id` integer NOT NULL UNIQUE
+)
+;
+ALTER TABLE `django_authopenid_userassociation` ADD CONSTRAINT user_id_refs_id_f63a9e7163d208d FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `django_authopenid_userpasswordqueue` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL UNIQUE,
+ `new_password` varchar(30) NOT NULL,
+ `confirm_key` varchar(40) NOT NULL
+)
+;
+ALTER TABLE `django_authopenid_userpasswordqueue` ADD CONSTRAINT user_id_refs_id_7f488ca76bcaaa4 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `django_authopenid_externallogindata` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `external_username` varchar(40) NOT NULL UNIQUE,
+ `external_session_data` longtext NOT NULL,
+ `user_id` integer NULL
+)
+;
+ALTER TABLE `django_authopenid_externallogindata` ADD CONSTRAINT user_id_refs_id_462c0ee2c3e5e139 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `auth_permission` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `name` varchar(50) NOT NULL,
+ `content_type_id` integer NOT NULL,
+ `codename` varchar(100) NOT NULL,
+ UNIQUE (`content_type_id`, `codename`)
+)
+;
+ALTER TABLE `auth_permission` ADD CONSTRAINT content_type_id_refs_id_6bc81a32728de91f FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
+CREATE TABLE `auth_group` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `name` varchar(80) NOT NULL UNIQUE
+)
+;
+CREATE TABLE `auth_user` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `username` varchar(30) NOT NULL UNIQUE,
+ `first_name` varchar(30) NOT NULL,
+ `last_name` varchar(30) NOT NULL,
+ `email` varchar(75) NOT NULL,
+ `password` varchar(128) NOT NULL,
+ `is_staff` bool NOT NULL,
+ `is_active` bool NOT NULL,
+ `is_superuser` bool NOT NULL,
+ `last_login` datetime NOT NULL,
+ `date_joined` datetime NOT NULL,
+ `is_approved` bool NOT NULL,
+ `email_isvalid` bool NOT NULL,
+ `email_key` varchar(32) NULL,
+ `reputation` integer UNSIGNED NOT NULL,
+ `gravatar` varchar(32) NOT NULL,
+ `gold` smallint NOT NULL,
+ `silver` smallint NOT NULL,
+ `bronze` smallint NOT NULL,
+ `questions_per_page` smallint NOT NULL,
+ `last_seen` datetime NOT NULL,
+ `real_name` varchar(100) NOT NULL,
+ `website` varchar(200) NOT NULL,
+ `location` varchar(100) NOT NULL,
+ `date_of_birth` date NULL,
+ `about` longtext NOT NULL
+)
+;
+CREATE TABLE `auth_message` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `message` longtext NOT NULL
+)
+;
+ALTER TABLE `auth_message` ADD CONSTRAINT user_id_refs_id_7837edc69af0b65a FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE TABLE `auth_group_permissions` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `group_id` integer NOT NULL,
+ `permission_id` integer NOT NULL,
+ UNIQUE (`group_id`, `permission_id`)
+)
+;
+ALTER TABLE `auth_group_permissions` ADD CONSTRAINT group_id_refs_id_2ccea4c93cea63fe FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`);
+ALTER TABLE `auth_group_permissions` ADD CONSTRAINT permission_id_refs_id_4de83ca7792de1 FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`);
+CREATE TABLE `auth_user_groups` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `group_id` integer NOT NULL,
+ UNIQUE (`user_id`, `group_id`)
+)
+;
+ALTER TABLE `auth_user_groups` ADD CONSTRAINT user_id_refs_id_1993cb70831107f1 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `auth_user_groups` ADD CONSTRAINT group_id_refs_id_321a8efef0ee9890 FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`);
+CREATE TABLE `auth_user_user_permissions` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `permission_id` integer NOT NULL,
+ UNIQUE (`user_id`, `permission_id`)
+)
+;
+ALTER TABLE `auth_user_user_permissions` ADD CONSTRAINT user_id_refs_id_166738bf2045483 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+ALTER TABLE `auth_user_user_permissions` ADD CONSTRAINT permission_id_refs_id_6d7fb3c2067e79cb FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`);
+COMMIT;
diff --git a/templates/404.html b/templates/404.html
index 2fa38f99..227de3ae 100644
--- a/templates/404.html
+++ b/templates/404.html
@@ -33,7 +33,7 @@
</u>
</div>
<script type="text/javascript">
- var GOOG_FIXURL_LANG = 'zh-cn';
+ var GOOG_FIXURL_LANG = '{{settings.LANGUAGE_CODE}}';
var GOOG_FIXURL_SITE = '{{site_url}}';
</script>
<script type="text/javascript" src="http://linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
diff --git a/templates/about.html b/templates/about.html
index eaf0d591..2ca75500 100644
--- a/templates/about.html
+++ b/templates/about.html
@@ -12,10 +12,30 @@
</div>
<div class="content">
- <p>edit file templates/about.html. Below are just suggestions of what can go here</p>
- <p>what is your site for?</p>
- <p>how does it work? what are roles of members?</p>
- <p>is there a place to find out more about this website?</p>
+ <p><strong>NMR Wiki <span class="orange">Q&amp;A</span></strong> is a collaboratively edited question
+ and answer site created for the <strong>Magnetic Resonance</strong> community, i.e. those people who
+ work in the fields of <strong>NMR</strong>, <strong>EPR</strong>, <strong>MRI</strong></strong>, etc.
+ NMR Wiki Q&amp;A is affiliated with <strong><a href="http://nmrwiki.org">NMR Wiki</a></strong> -
+ the public wiki knowledge base about the Magnetic Resonance, which currently counts ~300 registered users. The most useful information collected here
+ will be further distilled on the wiki site.
+ </p>
+ <p>Here you can <strong>ask</strong> and <strong>answer</strong> questions, <strong>comment</strong>
+ and <strong>vote</strong> for the questions of others and their answers. Both questions and answers
+ <strong>can be revised</strong> and improved. Questions can be <strong>tagged</strong> with
+ the relevant keywords to simplify future access and organize the accumulated material.
+ </p>
+
+ <p>This <span class="orange">Q&amp;A</span> site is moderated by its members, hopefully - including yourself!
+ Moderation rights are gradually assigned to the site users based on the accumulated <strong>"reputation"</strong>
+ points. These points are added to the users account when others vote for his/her questions or answers.
+ These points (very) roughly reflect the level of trust of the community.
+ </p>
+ <p>No points are necessary to ask or answer the questions - so please -
+ <strong><a href="{% url user_signin %}">join us!</a></strong>
+ </p>
+ <p>
+ If you would like to find out more about this site - please see <strong><a href="{% url faq %}">frequently asked questions</a></strong>.
+ </p>
</div>
{% endblock %}
<!-- end template about.html -->
diff --git a/templates/answer_edit.html b/templates/answer_edit.html
index 8baa7c1e..cd247a3c 100644
--- a/templates/answer_edit.html
+++ b/templates/answer_edit.html
@@ -1,14 +1,15 @@
{% extends "base.html" %}
<!-- template answer_edit.html -->
{% load i18n %}
+{% load extra_tags %}
{% block title %}{% spaceless %}{% trans "Edit answer" %}{% 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>
- <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
- <script type='text/javascript' src='/content/js/wmd/showdown.js'></script>
- <script type='text/javascript' src='/content/js/wmd/wmd.js'></script>
- <link rel="stylesheet" type="text/css" href="/content/js/wmd/wmd.css" />
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.post.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/showdown.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/wmd.js" %}'></script>
+ <link rel="stylesheet" type="text/css" href="{% href "/content/js/wmd/wmd.css" %}" />
<script type="text/javascript">
$().ready(function(){
diff --git a/templates/answer_edit_tips.html b/templates/answer_edit_tips.html
index 33e4e242..c390da06 100644
--- a/templates/answer_edit_tips.html
+++ b/templates/answer_edit_tips.html
@@ -16,7 +16,9 @@
{% trans "be clear and concise" %}
</li>
</ul>
- <a href="{% url faq %}" target="_blank" title="{% trans "see frequently asked questions" %}" style="float:right;position:relative">faq »</a>
+ <p class='info-box-follow-up-links'>
+ <a href="{% url faq %}" target="_blank" title="{% trans "see frequently asked questions" %}">faq »</a>
+ </p>
</div>
</div>
@@ -46,6 +48,8 @@
{% 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>
+ <p class='info-box-follow-up-links'>
+ <a href="http://en.wikipedia.org/wiki/Markdown" target="_blank">{% trans "learn more about Markdown" %} »</a>
+ </p>
</div>
<!-- end template answer_edit_tips.html -->
diff --git a/templates/ask.html b/templates/ask.html
index 447ee749..2efd9864 100644
--- a/templates/ask.html
+++ b/templates/ask.html
@@ -1,14 +1,15 @@
{% extends "base.html" %}
<!-- template ask.html -->
{% load i18n %}
+{% load extra_tags %}
{% block title %}{% spaceless %}{% trans "Ask a question" %}{% 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>
- <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
- <script type='text/javascript' src='/content/js/wmd/showdown.js'></script>
- <script type='text/javascript' src='/content/js/wmd/wmd.js'></script>
- <link rel="stylesheet" type="text/css" href="/content/js/wmd/wmd.css" />
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.post.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/showdown.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/wmd.js" %}'></script>
+ <link rel="stylesheet" type="text/css" href="{% href "/content/js/wmd/wmd.css" %}" />
<script type="text/javascript">
$().ready(function(){
//set current module button style
@@ -70,7 +71,9 @@
{% 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 %}
+ {% blocktrans with request.user.email as email %}must have valid {{email}} to post,
+ see {{email_validation_faq_url}}
+ {% endblocktrans %}
</div>
{% endif %}
{% endifequal %}
@@ -93,9 +96,11 @@
<td>
<span id="pre-collapse" title="{% trans "Toggle the real time Markdown editor preview" %}">{% trans "toggle preview" %}</span>
</td>
+ {% if settings.WIKI_ON %}
<td style="text-align:right;">
{{ form.wiki }} <span style="font-weight:normal;cursor:help" title="{{form.wiki.help_text}}">{{ form.wiki.label_tag }} </span>
</td>
+ {% endif %}
</tr>
</table>
diff --git a/templates/authopenid/changeemail.html b/templates/authopenid/changeemail.html
index 09857d17..94d1881c 100644
--- a/templates/authopenid/changeemail.html
+++ b/templates/authopenid/changeemail.html
@@ -1,37 +1,44 @@
{% extends "base_content.html" %}
{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Change email" %}{% endspaceless %}{% endblock %}
{% block content %}
<!-- changeemail.html action_type={{action_type}}-->
{% ifequal action_type "change" %}
<div id="main-bar" class="headNormal">
- {% trans "Change email" %}
+ {% if user.email %}
+ {% trans "Change email" %}
+ {% else %}
+ {% trans "Save your email address" %}
+ {% endif %}
</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>
+ {% if user.email %}
+ {% blocktrans %}change {{email}} info{% endblocktrans %}
+ {% else %}
+ {% blocktrans %}here is why email is required, see {{gravatar_faq_url}}{% endblocktrans %}
{% endif %}
</p>
- {% endif %}
-
{% if msg %}
- <p class="errors">{{ msg }}</p>
+ <p class="error">{{ msg }}</p>
{% endif %}
<div class="aligned">
<form action="." method="post" accept-charset="utf-8">
-
- <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>-->
+ {% if next %}
+ <input type="hidden" name="next" value="{{next}}"/>
+ {% endif %}
+ <div class="form-row-vertical">
+ <label for="id_email">{% if user.email %}{% trans "Your new Email" %}{% else %}{% trans "Your Email" %}{% endif %}</label>
+ {% if form.email.errors %}
+ <p class="error">{{form.email.errors|join:", "}}</p>
+ {% endif %}
+ {{ form.email }}
+ </div>
<div class="submit-row">
- <input class="submit" type="submit" name="change_email" value="{% trans "Change email" %}">
+ <input class="submit" type="submit" name="change_email" value="{% if user.email %}{% trans "Change email" %}{% else %}{% trans "Save Email" %}{% endif %}">
+ {% if user.email %}
<input class="submit" type="submit" name="cancel" value="{% trans "Cancel" %}">
+ {% endif %}
</div>
</form>
@@ -42,7 +49,7 @@
{% trans "Validate email" %}
</div>
<p class="message">
- {% blocktrans %}validate {{email}} info{% endblocktrans %}
+ {% blocktrans %}validate {{email}} info or go to {{change_email_url}}{% endblocktrans %}
</p>
{% endifequal %}
{% ifequal action_type "keep" %}
@@ -50,7 +57,7 @@
{% trans "Email not changed" %}
</div>
<p class="message">
- {% blocktrans %}old {{email}} kept{% endblocktrans %}
+ {% blocktrans %}old {{email}} kept, if you like go to {{change_email_url}}{% endblocktrans %}
</p>
{% endifequal %}
{% ifequal action_type "done_novalidate" %}
diff --git a/templates/authopenid/changeopenid.html b/templates/authopenid/changeopenid.html
index 9b5a196a..d01788fb 100644
--- a/templates/authopenid/changeopenid.html
+++ b/templates/authopenid/changeopenid.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
<!-- changeopenid.html -->
{% load i18n %}
-
+{% block title %}{% spaceless %}{% trans "Change OpenID" %}{% endspaceless %}{% endblock %}
{% block content %}
<div id="main-bar" class="">
<h3>
diff --git a/templates/authopenid/changepw.html b/templates/authopenid/changepw.html
index 0e90b172..8b059544 100644
--- a/templates/authopenid/changepw.html
+++ b/templates/authopenid/changepw.html
@@ -1,34 +1,17 @@
{% extends "base.html" %}
<!-- changepw.html -->
{% load i18n %}
-
-{% block head %}
-
-{% endblock %}
-
-
-
+{% block head %}{% endblock %}
+{% block title %}{% spaceless %}{% trans "Change password" %}{% endspaceless %}{% endblock %}
{% block content %}
-<div id="main-bar" class="">
- <h3>
- {% trans "Account: change password" %}
- </h3>
-</div>
-<p>{% blocktrans %}This is where you can change your password. Make sure you remember it!{% endblocktrans %}</p>
-{% if form.errors %}
-<p class="errors">{% trans "Please correct errors below:" %}<br />
-{{ form.errors }}
-</p>
-{% endif %}
-
+<div class="headNormal">{% trans "Account: change password" %}</div>
+<p class="message">{% blocktrans %}This is where you can change your password. Make sure you remember it!{% endblocktrans %}</p>
<div class="aligned">
<form action="." method="post" accept-charset="utf-8">
-
- <div id="form-row"><label for="id_oldpw">{% trans "Current password" %}</label>{{ form.oldpw }}</div>
- <div id="form-row"><label for="id_password1">{% trans "New password" %}</label>{{ form.password1 }}</div>
- <div id="form-row"><label for="id_password2">{% trans "New password again" %}</label>{{ form.password2 }}</div>
- <p><input type="submit" value="{% trans "Change password" %}"></p>
-
+ <ul id="changepw-form" class="form-horizontal-rows">
+ {{form.as_ul}}
+ </ul>
+ <div class="submit-row"><input type="submit" class="submit" value="{% trans "Change password" %}" /></div>
</form>
</div>
{% endblock %}
diff --git a/templates/authopenid/complete.html b/templates/authopenid/complete.html
index f29b7670..e3c12ae5 100644
--- a/templates/authopenid/complete.html
+++ b/templates/authopenid/complete.html
@@ -1,5 +1,19 @@
{% extends "base_content.html" %}
<!-- complete.html -->
+{% comment %}
+views calling this template:
+* django_authopenid.views.register with login_type='openid'
+* django_authopenid.views.signin - with login_type='legacy'
+
+parameters:
+* provider
+* login_type openid|legacy
+* username (same as screen name or username in the models, and nickname in openid sreg)
+* form1 - OpenidRegisterForm
+* form2 - OpenidVerifyForm not clear what this form is supposed to do, not used for legacy
+* email_feeds_form forum.forms.EditUserEmailFeedsForm
+* openid_username_exists
+{% endcomment %}
{% load i18n %}
{% block head %}{% endblock %}
{% block title %}{% spaceless %}{% trans "Connect your OpenID with this site" %}{% endspaceless %}{% endblock %}
@@ -9,25 +23,34 @@
</div>
<div id="completetxt" >
<div class="message">
- {% blocktrans %}register new {{provider}} account info{% endblocktrans %}
+ {% ifequal login_type 'openid' %}
+ {% blocktrans %}register new {{provider}} account info, see {{gravatar_faq_url}}{% endblocktrans %}
+ {% else %}
+ {% ifequal login_type 'legacy' %}
+ {% if external_login_name_is_taken %}
+ {% blocktrans %}{{username}} already exists, choose another name for
+ {{provider}}. Email is required too, see {{gravatar_faq_url}}
+ {% endblocktrans %}
+ {% else %}
+ {% blocktrans %}register new external {{provider}} account info, see {{gravatar_faq_url}}{% endblocktrans %}
+ {% endif %}
+ {% endifequal %}
+ {% endifequal %}
</div>
<p style="display:none">{% trans "This account already exists, please use another." %}</p>
</div>
{% if form1.errors %}
- <div class="errors">
- <span class="big">{% trans "Sorry, looks like we have some errors:" %}</span><br/>
- <ul class="error-list">
- {% if form1.username.errors %}
- <li><span class="error">{{ form1.username.errors|join:", " }}</span></li>
- {% endif %}
- {% if form1.email.errors %}
- <li><span class="error">{{ form1.email.errors|join:", " }}</span></li>
- {% endif %}
+ <ul class="errorlist">
+ {% if form1.non_field_errors %}
+ {% for error in form1.non_field_errors %}
+ <li>{{error}}</li>
+ {% endfor %}
+ {% endif %}
</ul>
- </div>
{% endif %}
- {% if form2.errors %}
+ {% comment %}
+ {% if form2.errors %}<!--form2 is dysfunctional so commented out -->
<div class="errors">
<span class="big">{% trans "Sorry, looks like we have some errors:" %}</span><br/>
<ul class="error-list">
@@ -40,29 +63,58 @@
</ul>
</div>
{% endif %}
+ {% endcomment %}
<div class="login">
+ {% ifequal login_type 'openid' %}
<form name="fregister" action="{% url user_register %}" method="POST">
- {{ form.next }}
- <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>
+ {% else %}
+ <form name="fregister" action="{% url user_signin %}" method="POST">
+ {% endifequal %}
+ {{ form1.next }}
+ <div class="form-row-vertical">
+ <label for="id_username">{% trans "Screen name label" %}</label>
+ {% if form1.username.errors %}
+ <p class="error">{{ form1.username.errors|join:", " }}</p>
+ {% endif %}
+ {{ form1.username }}
+ </div>
+ <div class="form-row-vertical margin-bottom">
+ <label for="id_email">{% trans "Email address label" %}</label>
+ {% if form1.email.errors %}
+ <p class="error">{{ form1.email.errors|join:", " }}</p>
+ {% endif %}
+ {{ form1.email }}
+ </div>
+ <p class='nomargin'>{% trans "receive updates motivational blurb" %}</p>
+ {% include "edit_user_email_feeds_form.html" %}
+<<<<<<< HEAD:templates/authopenid/complete.html
+=======
+ <p class='nomargin'>{% trans "Tag filter tool will be your right panel, once you log in." %}</p>
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/authopenid/complete.html
<div class="submit-row"><input type="submit" class="submit" name="bnewaccount" value="{% trans "create account" %}"/></div>
</form>
</div>
+ {% comment %}<!-- this form associates openID with an existing password-protected account, not yet functional -->
+ {% if form2 %}
<div class="login" style="display:none">
<form name="fverify" action="{% url user_register %}" method="POST">
- {{ form.next }}
+ {{ form2.next }}
<fieldset style="padding:10px">
<legend class="big">{% trans "Existing account" %}</legend>
<div class="form-row"><label for="id_username">{% trans "user name" %}</label><br/>{{ form2.username }}</div>
<div class="form-row"><label for="id_passwordl">{% trans "password" %}</label><br/>{{ form2.password }}</div>
+ <p><span class='big strong'>(Optional) receive updates by email</span> - only sent when there are any.</p>
+ {% include "edit_user_email_feeds_form.html" %}
<!--todo double check translation from chinese 确认 = "Register" -->
<div class="submit-row">
<input type="submit" class="submit" name="bverify" value="{% trans "Register" %}"/>
- <a href="">{% trans "Forgot your password?" %}</a>
+ <a href="{% url user_sendpw %}">{% trans "Forgot your password?" %}</a>
</div>
</fieldset>
</form>
</div>
+ {% endif %}
+ {% endcomment %}
{% endblock %}
<!-- end complete.html -->
diff --git a/templates/authopenid/confirm_email.txt b/templates/authopenid/confirm_email.txt
index 29b5cd94..0b3b2505 100644
--- a/templates/authopenid/confirm_email.txt
+++ b/templates/authopenid/confirm_email.txt
@@ -5,8 +5,8 @@ Los detalles de su cuenta son:
Nombre de usuario: {{ username }}
Contraseña: {{ password }}
-Favor inicie sesión aquí:
-{{ site_url }}{% trans "signin/" %}
+{% trans "Please sign in here:" %}
+{{signup_url}}
Saludos,
El equipo administrador de Hasked.com
diff --git a/templates/authopenid/delete.html b/templates/authopenid/delete.html
index d39bc962..0f9f1c60 100644
--- a/templates/authopenid/delete.html
+++ b/templates/authopenid/delete.html
@@ -1,8 +1,7 @@
{% extends "base.html" %}
<!-- delete.html -->
{% load i18n %}
-
-
+{% block title %}{% spaceless %}{% trans "Delete account" %}{% endspaceless %}{% endblock %}
{% block content %}
<div id="main-bar" class="">
<h3>
diff --git a/templates/authopenid/external_legacy_login_info.html b/templates/authopenid/external_legacy_login_info.html
new file mode 100644
index 00000000..dda394c7
--- /dev/null
+++ b/templates/authopenid/external_legacy_login_info.html
@@ -0,0 +1,21 @@
+{% extends "base_content.html" %}
+<!--customize this template-->
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Traditional login information" %}{% endspaceless %}{% endblock %}
+{% block content %}
+<div class="headNormal">
+ {% trans "Traditional login information" %}
+</div>
+{% spaceless %}
+<div class="message">
+<<<<<<< HEAD:templates/authopenid/external_legacy_login_info.html
+fill in template templates/authopenid/external_legacy_login_info.html
+and explain how to change password, recover password, etc.
+<!--add info about your external login site here-->
+=======
+<!--add info about your external login site here-->
+{% trans "how to login with password through external login website" %}
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/authopenid/external_legacy_login_info.html
+</div>
+{% endspaceless %}
+{% endblock %}
diff --git a/templates/authopenid/sendpw.html b/templates/authopenid/sendpw.html
index 37091261..6241c811 100644
--- a/templates/authopenid/sendpw.html
+++ b/templates/authopenid/sendpw.html
@@ -2,35 +2,25 @@
<!-- sendpw.html -->
{% load i18n %}
{% block title %}{% spaceless %}{% trans "Send new password" %}{% endspaceless %}{% endblock %}
-
{% block content %}
-<div id="main-bar" class="">
- <h3>{% trans "Send new password" %}</h3>
-
-</div>
-<div class="paragraph">
-{% trans "Lost your password? No problem - here you can reset it." %}<br/>
-{% trans "Please enter your username below and new password will be sent to your registered e-mail" %}
+<div class="headNormal">
+ {% trans "Send new password" %}
</div>
-{% if form.errors %}
-<p class="errors"><span class="big">{% trans "Sorry, looks like we have some errors:" %}</span><br/>
- {% if form.username.errors %}
- <span class="error">{{ form.username.errors|join:", " }}</span>
- {% endif %}
+<p class="message">
+{% trans "password recovery information" %}
</p>
-{% endif %}
{% if msg %}
- <div class="paragraph error">{{ msg }}</div>
+ <p class="action-status"><span>{{msg}}</span><p>
{% endif %}
<div class="aligned">
<form action="." method="post" accept-charset="utf-8">
- <div id="form-row"><label for="id_username">{% trans "User name" %}:</label>{{ form.username }}</div>
-
- <p style="padding-top:10px"><input type="submit" value="{% trans "Reset password" %}"> <a href="{% url user_signin %}">{% trans "return to login" %}</a></p>
-
+ <ul id="emailpw-form" class="form-horizontal-rows">
+ {{form.as_ul}}
+ </ul>
+ <p style="padding-top:10px"><input type="submit" class="submit" value="{% trans "Reset password" %}" />
+ <a href="{% url user_signin %}"><span class="strong">{% trans "return to login" %}</span></a></p>
</form>
- <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 06653d4f..c4910d12 100644
--- a/templates/authopenid/sendpw_email.txt
+++ b/templates/authopenid/sendpw_email.txt
@@ -1,13 +1,9 @@
-Alguien ha solicitado restablecer su contraseña en {{ site_url }}.
-Si no fué usted, ignore este correo
+{% 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 %}
-Sus nuevo datos son:
-
-Nombre de usuario: {{ username }}
-Contraseña: {{ password }}
-
-Para hacer efectivo el cambio por favor visita:
-{{ site_url }}{{ url_confirm }}?key={{ confirm_key }}
+{% blocktrans %}email explanation how to use new {{password}} for {{username}}
+with the {{key_link}}{% endblocktrans %}
Saludos,
El Equipo administrador de Hasked.com
diff --git a/templates/authopenid/settings.html b/templates/authopenid/settings.html
index ecc16c72..66ea5953 100644
--- a/templates/authopenid/settings.html
+++ b/templates/authopenid/settings.html
@@ -1,7 +1,7 @@
{% extends "base_content.html" %}
<!-- settings.html -->
{% load i18n %}
-
+{% block title %}{% spaceless %}{% trans "Account functions" %}{% endspaceless %}{% endblock %}
{% block head %}
<style type="text/css" media="screen">
h4 {font-size:12pt;}
diff --git a/templates/authopenid/signin.html b/templates/authopenid/signin.html
index 60aa5e5d..1363661e 100644
--- a/templates/authopenid/signin.html
+++ b/templates/authopenid/signin.html
@@ -1,13 +1,13 @@
{% extends "base.html" %}
<!-- signin.html -->
{% load i18n %}
+{% load extra_tags %}
{% block title %}{% spaceless %}{% trans "User login" %}{% endspaceless %}{% endblock %}
{% block forejs %}
- <!--<script type="text/javascript" src="/content/js/jquery.openid.js?"></script>-->
- <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
- <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>
+ <link rel="stylesheet" type="text/css" media="screen" href="{% href "/content/jquery-openid/openid.css" %}"/>
+ <script type="text/javascript" src="{% href "/content/jquery-openid/jquery.openid.js" %}"></script>
<script type="text/javascript"> $().ready( function() { $("form.openid:eq(0)").openid(); })</script>
<!--<script type="text/javascript">
$().ready(function(){
@@ -44,25 +44,27 @@
<ul class="providers">
<li class="local" title="Local login">
<div class="logo_box local_login_box">
- <img src="/content/jquery-openid/images/local-login.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/local-login.png" %}" alt="your icon here" />
</div>
<span></span>
</li>
<li class="direct" title="Google">
<div class="logo_box google_box">
- <img src="/content/jquery-openid/images/google.gif" alt="icon" /><span>https://www.google.com/accounts/o8/id</span>
+ <img src="{% href "/content/jquery-openid/images/google.gif" %}" alt="icon" /><span>https://www.google.com/accounts/o8/id</span>
</div>
</li>
<li class="direct" title="Yahoo">
<div class="logo_box yahoo_box">
- <img src="/content/jquery-openid/images/yahoo.gif" alt="icon" /><span>http://yahoo.com/</span>
+ <img src="{% href "/content/jquery-openid/images/yahoo.gif" %}" alt="icon" /><span>http://yahoo.com/</span>
</div>
</li>
<li class="username" title="AOL screen name">
<div class="logo_box aol_box">
- <img src="/content/jquery-openid/images/aol.gif" alt="icon" /><span>http://openid.aol.com/<strong>username</strong></span>
+ <img src="{% href "/content/jquery-openid/images/aol.gif" %}" alt="icon" /><span>http://openid.aol.com/<strong>username</strong></span>
</div>
</li>
+ </ul>
+ <ul class="providers">
<!--<li class="openid" title="OpenID">
<div class="logo_box openid_box">
<img src="/content/jquery-openid/images/openid.gif" alt="icon" />
@@ -70,43 +72,43 @@
<span><strong>http://{your-openid-url}</strong></span>
</li>-->
<li class="openid first_tiny_li" title="OpenID URL">
- <img src="/content/jquery-openid/images/openidico16.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/openidico16.png" %}" alt="icon" />
<span>http://{your-openid-url}</span>
</li>
<li class="username" title="MyOpenID user name">
- <img src="/content/jquery-openid/images/myopenid-2.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/myopenid-2.png" %}" alt="icon" />
<span>http://<strong>username</strong>.myopenid.com/</span>
</li>
<li class="username" title="Flickr user name">
- <img src="/content/jquery-openid/images/flickr.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/flickr.png" %}" alt="icon" />
<span>http://flickr.com/<strong>username</strong>/</span>
</li>
<li class="username" title="Technorati user name">
- <img src="/content/jquery-openid/images/technorati-1.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/technorati-1.png" %}" alt="icon" />
<span>http://technorati.com/people/technorati/<strong>username</strong>/</span>
</li>
<li class="username" title="Wordpress blog name">
- <img src="/content/jquery-openid/images/wordpress.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/wordpress.png" %}" alt="icon" />
<span>http://<strong>username</strong>.wordpress.com</span>
</li>
<li class="username" title="Blogger blog name">
- <img src="/content/jquery-openid/images/blogger-1.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/blogger-1.png" %}" alt="icon" />
<span>http://<strong>username</strong>.blogspot.com/</span>
</li>
<li class="username" title="LiveJournal blog name">
- <img src="/content/jquery-openid/images/livejournal-1.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/livejournal-1.png" %}" alt="icon" />
<span>http://<strong>username</strong>.livejournal.com</span>
</li>
<li class="username" title="ClaimID user name">
- <img src="/content/jquery-openid/images/claimid-0.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/claimid-0.png" %}" alt="icon" />
<span>http://claimid.com/<strong>username</strong></span>
</li>
<li class="username" title="Vidoop user name">
- <img src="/content/jquery-openid/images/vidoop.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/vidoop.png" %}" alt="icon" />
<span>http://<strong>username</strong>.myvidoop.com/</span>
</li>
<li class="username" title="Verisign user name">
- <img src="/content/jquery-openid/images/verisign-2.png" alt="icon" />
+ <img src="{% href "/content/jquery-openid/images/verisign-2.png" %}" alt="icon" />
<span>http://<strong>username</strong>.pip.verisignlabs.com/</span>
</li>
</ul>
@@ -125,27 +127,17 @@
</fieldset>
<fieldset id='local_login_fs'>
<p>{% trans 'Enter your login name and password' %}</p>
- {% if form1.errors %}
- <p class="errors">
- <span class="big">{% trans "Sorry, looks like we have some errors:" %}</span><br/>
- <ul class="error-list">
- {% if form1.username.errors %}
- <li><span class="error">{{ form1.username.errors|join:", " }}</span></li>
- {% endif %}
- {% if form1.password.errors %}
- <li><span class="error">{{ form1.password.errors|join:", " }}</span></li>
- {% endif %}
- </ul>
- </p>
- {% endif %}
- <div><label for="username">{% trans "Login name" %}</label>
- {{form1.username}}<br/>
- <label for="password">{% trans "Password" %}</label>
- {{form1.password}}<br/>
+ {% if form1.errors %}
+ {{form1.non_field_errors.as_ul}}
+ {% endif %}
+ <div><p class="login"><label for="id_username">{% trans "Login name" %}</label>
+ {{form1.username}}</p>
+ <p class="login"><label for="id_password">{% trans "Password" %}</label>
+ {{form1.password}}</p>
<p id="local_login_buttons">
<input id="blogin" name="blogin" type="submit" value="{% trans "Login" %}" />
<a href="{% url user_signup %}">{% trans "Create account" %}</a><br/>
- <a href="{% url user_sendpw %}">{% trans "I forgot my password" %}</a>
+ <a href="{% url user_sendpw %}">{% trans "Forgot your password?" %}</a>
</p>
</div>
</fieldset>
@@ -170,9 +162,9 @@
</li>
</ul>
- <p>
- <a href="http://openid.net/what/" target="_blank" style="float:right;position:relative">{% trans "Find out more" %} »</a><br/>
- <a href="http://openid.net/get/" target="_blank" style="float:right;position:relative">{% trans "Get OpenID" %} »</a>
+ <p class="info-box-follow-up-links">
+ <a href="http://openid.net/what/" target="_blank">{% trans "Find out more" %} »</a><br/>
+ <a href="http://openid.net/get/" target="_blank">{% trans "Get OpenID" %} »</a>
</p>
</div>
{% endblock%}
diff --git a/templates/authopenid/signup.html b/templates/authopenid/signup.html
index 5e405d3f..45dfb51b 100644
--- a/templates/authopenid/signup.html
+++ b/templates/authopenid/signup.html
@@ -1,53 +1,22 @@
-{% extends "base.html" %}
+{% extends "base_content.html" %}
<!--signup.html-->
{% load i18n %}
{% block title %}{% spaceless %}{% trans "Signup" %}{% endspaceless %}{% endblock %}
{% block content %}
-<div id="main-bar" class="">
- <h3 >{% trans "Signup" %}</h3>
-
-</div>
-<div class="jointxt">
- <p>{% trans "We support two types of user registration: conventional username/password, and" %} <a href="{% url user_signin %}">{% trans "the OpenID method" %}</a>.</p>
-
- {% if form.errors %}
-
- <p class="errors">
- <span class="big">{% trans "Sorry, looks like we have some errors" %}</span><br/>
- <ul class="error-list">
- {% if form.username.errors %}
- <li><span class="error">{{ form.username.errors|join:", " }}</span></li>
- {% endif %}
- {% if form.email.errors %}
- <li><span class="error">{{ form.email.errors|join:", " }}</span></li>
- {% endif %}
- {% if form.password2.errors %}
- <li><span class="error">{{ form.password2.errors|join:", " }}
- </span></li>
- {% endif %}
- </ul>
- </p>
- {% endif %}
+<div class="headNormal">
+ {% trans "Create login name and password" %}
</div>
- <form action="{% url user_signup %}" method="post" accept-charset="utf-8">
- <fieldset class="fieldset">
- <legend class="big">{% trans "Conventional registration" %}</legend>
- <div class="form-row"><label for="id_username">{% trans "choose a user name" %}:</label><br/>{{ form.username }}</div>
-
- <div class="form-row"><label for="id_email">{% trans "your email address" %}:</label><br/>{{ form.email }}</div>
- <div class="form-row"><label for="id_password1">{% trans "choose password" %}:</label><br />{{ form.password1 }}</div>
- <div class="form-row"><label for="id_password2">{% trans "retype password" %}:</label><br/>{{ form.password2 }}</div>
- <div class="submit-row"><input type="submit" class="submit" value="{% trans "login" %}" >
- <a href="{% url user_signin %}">{% trans "back to login" %}</a></div>
- </fieldset>
- </form>
- <div style="display:none">
- <h2 class="signup">{% trans "Register with your OpenID" %}</h2>
- <form name="fopenid" action="{% url user_signin %}" method="post">
- <div class="form-row">{{ form2.openid_url }}</div>
- <div class="submit-row "><input name="bsignin" class="submit" type="submit" value="{% trans "Login with your OpenID" %}"></div>
- </form>
- </div>
+<p class="message">{% trans "Traditional signup info" %}</p>
+<form action="{% url user_signup %}" method="post" accept-charset="utf-8">
+ <ul class="form-horizontal-rows">
+ {{form.as_ul}}
+ </ul>
+ <p style="margin:10px 0px 0px 3px;">{% trans "receive updates motivational blurb" %}</p>
+ {% include "edit_user_email_feeds_form.html" %}
+ <div class="submit-row"><input type="submit" class="submit" value="{% trans "Create Account" %}" />
+ <strong>{% trans "or" %}
+ <a href="{% url user_signin %}">{% trans "return to OpenID login" %}</a></strong></div>
+</form>
{% endblock %}
<!--end signup.html-->
diff --git a/templates/badges.html b/templates/badges.html
index 4a1ba091..1902a3b0 100644
--- a/templates/badges.html
+++ b/templates/badges.html
@@ -19,7 +19,9 @@
<div class="badges" id="main-body" style="width:100%">
<p>
{% 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." %}
+ {% blocktrans %}Below is the list of available badges and number
+ of times each type of badge has been awarded. Give us feedback at {{feedback_faq_url}}.
+ {% endblocktrans %}
</p>
<div id="medalList">
{% for badge in badges %}
@@ -32,7 +34,7 @@
{% endfor %}
</div>
<div style="float:left;width:180px;">
- <a href="{{badge.get_absolute_url}}" title="{{ badge.get_type_display }} : {{ badge.description }}" class="medal"><span class="badge{{ badge.type }}">&#9679;</span>&nbsp;{{ badge.name }}</a><strong> &#x2715; {{ badge.awarded_count|intcomma }}</strong>
+ <a href="{{badge.get_absolute_url}}" title="{{ badge.get_type_display }} : {{ badge.description }}" class="medal"><span class="badge{{ badge.type }}">&#9679;</span>&nbsp;{{ badge.name }}</a><strong> &#215; {{ badge.awarded_count|intcomma }}</strong>
</div>
<p style="float:left;width:350px;">
{{ badge.description }}
diff --git a/templates/base.html b/templates/base.html
index 0f568f73..b4751be1 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -1,6 +1,7 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- template base.html -->
{% load extra_filters %}
+{% load extra_tags %}
{% load i18n %}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
@@ -12,16 +13,21 @@
{% if settings.GOOGLE_SITEMAP_CODE %}
<meta name="verify-v1" content="{{settings.GOOGLE_SITEMAP_CODE}}" />
{% endif %}
- <link rel="shortcut icon" href="/content/images/favicon.ico" />
- <link href="/content/style/style.css" rel="stylesheet" type="text/css" />
+ <link rel="shortcut icon" href="{% href "/content/images/favicon.ico" %}" />
+ <link href="{% href "/content/style/style.css" %}" rel="stylesheet" type="text/css" />
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">google.load("jquery", "1.2.6");</script>
<script type="text/javascript">
+<<<<<<< HEAD:templates/base.html
var i18nLang = '{{settings.LANGUAGE_CODE}}';
+=======
+ var i18nLang = '{{settings.LANGUAGE_CODE}}';
+ var scriptUrl = '/{{settings.FORUM_SCRIPT_ALIAS}}'
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/base.html
</script>
- <script type='text/javascript' src='/content/js/com.cnprog.i18n.js'></script>
- <script type='text/javascript' src='/content/js/jquery.i18n.js'></script>
- <script type='text/javascript' src='/content/js/com.cnprog.utils.js'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.i18n.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.i18n.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.utils.js" %}'></script>
<!--<script type="text/javascript">
var uservoiceJsHost = ("https:" == document.location.protocol) ? "https://uservoice.com" : "http://cdn.uservoice.com";
document.write(unescape("%3Cscript src='" + uservoiceJsHost + "/javascripts/widgets/tab.js' type='text/javascript'%3E%3C/script%3E"))
@@ -39,7 +45,7 @@
})
</script>-->
<!-- todo move this to settings -->
- {% if messages %}
+ {% if user_messages %}
<style type="text/css">
body { margin-top:2.4em; }
</style>
@@ -57,8 +63,8 @@
<body>
<div class="notify" style="display:none">
{% autoescape off %}
- {% if messages %}
- {% for message in messages %}
+ {% if user_messages %}
+ {% for message in user_messages %}
<p class="darkred">{{ message }}<p>
{% endfor %}
{% endif %}
@@ -73,7 +79,6 @@
{% endblock%}
</div>
-
<div id="CARight">
{% block sidebar%}
{% endblock%}
diff --git a/templates/base_content.html b/templates/base_content.html
index a05198f5..12297215 100644
--- a/templates/base_content.html
+++ b/templates/base_content.html
@@ -1,24 +1,36 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- base_content.html -->
{% load i18n %}
+{% load extra_tags %}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<<<<<<< HEAD:templates/base_content.html
<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" />
+=======
+ {% if settings.GOOGLE_SITEMAP_CODE %}
+ <meta name="verify-v1" content="{{ settings.GOOGLE_SITEMAP_CODE }}" />
+ {% endif %}
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/base_content.html
+ <link rel="shortcut icon" href="{% href "/content/images/favicon.ico" %}" />
+ <link href="{% href "/content/style/style.css" %}" rel="stylesheet" type="text/css" />
{% spaceless %}
{% block forestyle %}{% endblock %}
{% endspaceless %}
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">google.load("jquery", "1.2.6");</script>
<script type="text/javascript">
+<<<<<<< HEAD:templates/base_content.html
var i18nLang = '{{ settings.LANGUAGE_CODE }}';
+=======
+ var i18nLang = '{{ settings.LANGUAGE_CODE }}';
+ var scriptUrl = '/{{settings.FORUM_SCRIPT_ALIAS}}'
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/base_content.html
</script>
- <script type='text/javascript' src='/content/js/com.cnprog.i18n.js'></script>
- <script type='text/javascript' src='/content/js/jquery.i18n.js'></script>
- <script type='text/javascript' src='/content/js/com.cnprog.utils.js'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.i18n.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.i18n.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.utils.js" %}'></script>
<!-- <script type="text/javascript">
var uservoiceJsHost = ("https:" == document.location.protocol) ? "https://uservoice.com" : "http://cdn.uservoice.com";
@@ -39,7 +51,7 @@
</script>-->
<!-- todo move this to settings-->
- {% if messages %}
+ {% if user_messages %}
<style type="text/css">
body { margin-top:2.4em; }
</style>
@@ -58,8 +70,8 @@
<body>
<div class="notify" style="display:none">
{% autoescape off %}
- {% if messages %}
- {% for message in messages %}
+ {% if user_messages %}
+ {% for message in user_messages %}
<p class="darkred">{{ message }}<p>
{% endfor %}
{% endif %}
diff --git a/templates/book.html b/templates/book.html
index cc6fc77b..e83268e4 100644
--- a/templates/book.html
+++ b/templates/book.html
@@ -85,12 +85,12 @@
{% if question.favourite_count %}
{% if question.favorited_myself %}
<div class="favorites-count">
- <img title="{% trans "this question was selected as favorite" %} {{question.favourite_count}} {% trans "number of times" %}" src="/content/images/vote-favorite-on.png">
+ <img title="{% trans "this question was selected as favorite" %} {{question.favourite_count}} {% trans "number of times" %}" src="{% href "/content/images/vote-favorite-on.png" %}">
<div><b>{{question.favourite_count|intcomma}}</b></div>
</div>
{% else %}
<div class="favorites-count-off">
- <img title="{% trans "this question was selected as favorite" %} {{question.favourite_count}} {% trans "number of times" %}" src="/content/images/vote-favorite-off.png">
+ <img title="{% trans "this question was selected as favorite" %} {{question.favourite_count}} {% trans "number of times" %}" src="{% href "/content/images/vote-favorite-off.png" %}">
<div><b>{{question.favourite_count|intcomma}}</b></div>
</div>
{% endif %}
@@ -144,7 +144,7 @@
</div>
<div class="bookFeed">
<div id="feeds">
- <a href="/feeds/rss" title="{% trans "subscribe to book RSS feed" %}">{% trans "subscribe to the questions feed" %}</a>
+ <a href="{% href "/feeds/rss" %} " title="{% trans "subscribe to book RSS feed" %}">{% trans "subscribe to the questions feed" %}</a>
</div>
</div>
diff --git a/templates/content/images/blue-up-arrow-h18px.png b/templates/content/images/blue-up-arrow-h18px.png
new file mode 100644
index 00000000..e1f29e86
--- /dev/null
+++ b/templates/content/images/blue-up-arrow-h18px.png
Binary files differ
diff --git a/templates/content/images/close-small-dark.png b/templates/content/images/close-small-dark.png
new file mode 100644
index 00000000..280c1fc7
--- /dev/null
+++ b/templates/content/images/close-small-dark.png
Binary files differ
diff --git a/templates/content/images/gray-up-arrow-h18px.png b/templates/content/images/gray-up-arrow-h18px.png
new file mode 100644
index 00000000..78767445
--- /dev/null
+++ b/templates/content/images/gray-up-arrow-h18px.png
Binary files differ
diff --git a/templates/content/images/cnprog_logo_200_56.gif b/templates/content/images/logo.gif
index ab690de2..ab690de2 100644
--- a/templates/content/images/cnprog_logo_200_56.gif
+++ b/templates/content/images/logo.gif
Binary files differ
diff --git a/templates/content/images/logo.png b/templates/content/images/logo.png
index 640eb1da..b53732e3 100644
--- a/templates/content/images/logo.png
+++ b/templates/content/images/logo.png
Binary files differ
diff --git a/templates/content/jquery-openid/jquery.openid.js b/templates/content/jquery-openid/jquery.openid.js
index 6486fd39..763af2c6 100644
--- a/templates/content/jquery-openid/jquery.openid.js
+++ b/templates/content/jquery-openid/jquery.openid.js
@@ -36,7 +36,7 @@ $.fn.openid = function() {
};
var local = function() {
var $li = $(this);
- $li.parent().find('li').removeClass('highlight');
+ $('#openid_form .providers li').removeClass('highlight');
$li.addClass('highlight');
$usrfs.hide();
$idfs.hide();
@@ -47,7 +47,7 @@ $.fn.openid = function() {
var direct = function() {
var $li = $(this);
- $li.parent().find('li').removeClass('highlight');
+ $('#openid_form .providers li').removeClass('highlight');
$li.addClass('highlight');
$usrfs.fadeOut('slow');
$localfs.fadeOut('slow');
@@ -59,7 +59,7 @@ $.fn.openid = function() {
var openid = function() {
var $li = $(this);
- $li.parent().find('li').removeClass('highlight');
+ $('#openid_form .providers li').removeClass('highlight');
$li.addClass('highlight');
$usrfs.hide();
$localfs.hide();
@@ -71,7 +71,7 @@ $.fn.openid = function() {
var username = function() {
var $li = $(this);
- $li.parent().find('li').removeClass('highlight');
+ $('#openid_form .providers li').removeClass('highlight');
$li.addClass('highlight');
$idfs.hide();
$localfs.hide();
diff --git a/templates/content/jquery-openid/openid.css b/templates/content/jquery-openid/openid.css
index 2ec8adf4..88960b56 100644
--- a/templates/content/jquery-openid/openid.css
+++ b/templates/content/jquery-openid/openid.css
@@ -1,28 +1,35 @@
-fieldset {border-style:none;}
+fieldset { border-style:none; }
img {border-style:none;}
-.logo_box {display:inline-block;width:90px;height:40px;background:white;border:1px solid #dddddd;}
+.logo_box {display:inline-block;float:left;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;}
-.local_login_box img {margin-top:9px;}
+.local_login_box img {margin-top:2px;margin-left:-3px;}
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 {float:left; padding:4px;display:inline-block;}
+form.openid ul li div {display:inline-block;}
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}
+form.openid label {display:block; font-weight:bold;}
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 {
+ -moz-border-radius:4px;
+ -webkit-border-radius:4px;
+ background: #DCDCDC;
+ padding:10px;
+ display:inline-block;
+ float:left;
+}
form.openid p {margin-bottom:4px;}
form.openid fieldset div p {padding:0px;margin:0px;}
+form.openid fieldset div p.login {padding:0px;margin:0 0 10px 0;}
form.openid label {
display:inline-block;
font-weight:normal;
@@ -47,3 +54,16 @@ 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}
+
+form.openid ul.errorlist {
+ border: none;
+ list-style-position:inside;
+ list-style-type: disc;
+ margin-bottom:5px;
+}
+form.openid ul.errorlist li {
+ text-align: left;
+ margin: 5px;
+ float: none;
+ color:blue;
+}
diff --git a/templates/content/js/com.cnprog.admin.js b/templates/content/js/com.cnprog.admin.js
new file mode 100644
index 00000000..7e91af79
--- /dev/null
+++ b/templates/content/js/com.cnprog.admin.js
@@ -0,0 +1,17 @@
+$().ready( function(){
+ var options = {
+ success: function(a,b){$('.admin #action_status').html($.i18n._('changes saved'));},
+ dataType:'json',
+ timeout:5000,
+<<<<<<< HEAD:templates/content/js/com.cnprog.admin.js
+ url: $.i18n._('/') + $.i18n._('moderate-user/') + viewUserID + '/'
+=======
+ url: scriptUrl + $.i18n._('moderate-user/') + viewUserID + '/'
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.admin.js
+ };
+ var form = $('.admin #moderate_user_form').ajaxForm(options);
+ var box = $('.admin input#id_is_approved').click(function(){
+ $('.admin #action_status').html($.i18n._('sending data...'));
+ form.ajaxSubmit(options);
+ });
+});
diff --git a/templates/content/js/com.cnprog.i18n.js b/templates/content/js/com.cnprog.i18n.js
index 6ba8b59d..018927aa 100644
--- a/templates/content/js/com.cnprog.i18n.js
+++ b/templates/content/js/com.cnprog.i18n.js
@@ -20,7 +20,7 @@ var i18nZh = {
'post recovered':"操作成功!该帖子已被恢复。",
'post deleted':"操作成功!该帖子已删除。",
'add comment':'添加评论',
- 'community reputation points':'社区积分',
+ 'community karma points':'社区积分',
'to comment, need':'评论需要',
'delete this comment':'删除此评论',
'hide comments':"隐藏评论",
@@ -53,27 +53,32 @@ var i18nZh = {
'redo':'重做',
'enter image url':'<b>输入图片地址</b></p><p>示例:<br />http://www.example.com/image.jpg \"我的截图\"',
'enter url':'<b>输入Web地址</b></p><p>示例:<br />http://www.cnprog.com/ \"我的网站\"</p>"',
- 'upload image':'或者上传本地图片:',
+ 'upload image':'或者上传本地图片:'
};
var i18nEn = {
- '>15 points requried to upvote':'>15 points requried to upvote ',
+<<<<<<< HEAD:templates/content/js/com.cnprog.i18n.js
+ '/':'/',
+=======
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.i18n.js
+ 'need >15 points to report spam':'need >15 points to report spam ',
+ '>15 points requried to upvote':'>15 points required 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 ',
+ 'anonymous users cannot vote':'sorry, anonymous users cannot vote ',
+ 'anonymous users cannot select favorite questions':'sorry, anonymous users cannot select favorite questions ',
+ 'to comment, need': '(to comment other people\'s posts, karma ',
'please see':'please see ',
- 'community reputation points':' ',
+ 'community karma points':' or more is necessary) - ',
'upload image':'Upload image:',
'enter image url':'enter URL of the image, e.g. http://www.example.com/image.jpg \"image title\"',
'enter url':'enter Web address, e.g. http://www.example.com \"page title\"',
'daily vote cap exhausted':'sorry, you\'ve used up todays vote cap',
- 'cannot pick own answer as best':'cannot accept own answer',
- 'cannot revoke old vote':'older votes cannot be revoked',
+ 'cannot pick own answer as best':'sorry, you cannot accept your own answer',
+ 'cannot revoke old vote':'sorry, older votes cannot be revoked',
'please confirm offensive':'are you sure this post is offensive, contains spam, advertising, malicious remarks, etc.?',
- 'flag offensive cap exhausted':'sorry, you\'ve used up todays cap of flagging offensive messages',
+ 'flag offensive cap exhausted':'sorry, you\'ve used up todays cap of flagging offensive messages ',
'confirm delete':'are you sure you want to delete this?',
- 'anonymous users cannot delete/undelete':'anonymous users cannot delete or undelete posts',
+ 'anonymous users cannot delete/undelete':'sorry, anonymous users cannot delete or undelete posts',
'post recovered':'your post is now restored!',
'post deleted':'your post has been deleted',
'confirm delete comment':'do you really want to delete this comment?',
@@ -82,6 +87,9 @@ var i18nEn = {
'content minchars': 'please enter more than {0} characters',
'title minchars':"please enter at least {0} characters",
'characters':'characters left',
+ 'cannot vote for own posts':'sorry, you cannot vote for your own posts',
+ 'cannot flag message as offensive twice':'cannot flag message as offensive twice ',
+ '>100 points required to downvote':'>100 points required to downvote '
};
var i18nEs = {
@@ -106,7 +114,7 @@ var i18nEs = {
'post recovered':"publicación recuperada",
'post deleted':"publicación borrada。",
'add comment':'agregar comentario',
- 'community reputation points':'reputación comunitaria',
+ 'community karma points':'reputación comunitaria',
'to comment, need':'para comentar, necesita reputación',
'delete this comment':'borrar este comentario',
'hide comments':"ocultar comentarios",
@@ -141,7 +149,7 @@ var i18nEs = {
'enter url':'introduzca direcciones web, ejemplo:<br />http://www.cnprog.com/ \"titulo del enlace\"</p>"',
'upload image':'cargar imagen:',
'questions/' : 'preguntas/',
- 'vote/' : 'votar/',
+ 'vote/' : 'votar/'
};
var i18n = {
diff --git a/templates/content/js/com.cnprog.post.js b/templates/content/js/com.cnprog.post.js
index aa6c51b6..a884b571 100644
--- a/templates/content/js/com.cnprog.post.js
+++ b/templates/content/js/com.cnprog.post.js
@@ -52,31 +52,33 @@ var Vote = function(){
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}}'>"
+
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ var pleaseLogin = "<a href='" + $.i18n._("/") + $.i18n._("account/") + $.i18n._("signin/")
+ + "?next=" + $.i18n._("/") + $.i18n._("questions/") + "{{QuestionID}}'>"
+ $.i18n._('please login') + "</a>";
- var voteAnonymousMessage = $.i18n._('anonymous users cannot vote')
- + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
+
+ var pleaseSeeFAQ = $.i18n._('please see') + "<a href='" + $.i18n._("/") + $.i18n._("faq/") + "'>faq</a>";
+=======
+ var pleaseLogin = "<a href='" + scriptUrl + $.i18n._("account/") + $.i18n._("signin/")
+ + "?next=" + scriptUrl + $.i18n._("questions/") + "{{QuestionID}}'>"
+ $.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 pleaseSeeFAQ = $.i18n._('please see') + "<a href='" + scriptUrl + $.i18n._("faq/") + "'>faq</a>";
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
+
+ var favoriteAnonymousMessage = $.i18n._('anonymous users cannot select favorite questions')
+ var voteAnonymousMessage = $.i18n._('anonymous users cannot vote') + pleaseLogin;
+ var upVoteRequiredScoreMessage = $.i18n._('>15 points requried to upvote') + pleaseSeeFAQ;
+ var downVoteRequiredScoreMessage = $.i18n._('>100 points required to downvote') + pleaseSeeFAQ;
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 voteRequiredMoreVotes = $.i18n._('daily vote cap exhausted') + pleaseSeeFAQ;
+ var voteDenyCancelMessage = $.i18n._('cannot revoke old vote') + pleaseSeeFAQ;
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 offensiveAnonymousMessage = $.i18n._('anonymous users cannot flag offensive posts') + pleaseLogin;
+ var offensiveTwiceMessage = $.i18n._('cannot flag message as offensive twice') + pleaseSeeFAQ;
+ var offensiveNoFlagsLeftMessage = $.i18n._('flag offensive cap exhausted') + pleaseSeeFAQ;
+ var offensiveNoPermissionMessage = $.i18n._('need >15 points to report spam') + pleaseSeeFAQ;
var removeConfirmation = $.i18n._('confirm delete');
var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete');
var recoveredMessage = $.i18n._('post recovered');
@@ -94,7 +96,7 @@ var Vote = function(){
removeQuestion: 9,
removeAnswer:10,
questionSubscribeUpdates:11,
- questionUnsubscribeUpdates:12,
+ questionUnsubscribeUpdates:12
};
var getFavoriteButton = function(){
@@ -131,7 +133,7 @@ var Vote = function(){
};
var getOffensiveQuestionFlag = function(){
- var offensiveQuestionFlag = 'table[id=question-table] span[class='+ offensiveClassFlag +']';
+ var offensiveQuestionFlag = '#question-table span[class='+ offensiveClassFlag +']';
return $(offensiveQuestionFlag);
};
@@ -157,17 +159,30 @@ var Vote = function(){
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");
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ object.attr("src", $.i18n._("/") + "content/images/vote-arrow-"+ arrow + flag +".png");
+=======
+ object.attr("src", scriptUrl + "content/images/vote-arrow-"+ arrow + flag +".png");
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
// if undo voting, then undo the pair of arrows.
if(undo){
if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
- $(getQuestionVoteUpButton()).attr("src", "/content/images/vote-arrow-up.png");
- $(getQuestionVoteDownButton()).attr("src", "/content/images/vote-arrow-down.png");
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ $(getQuestionVoteUpButton()).attr("src", $.i18n._("/") + "content/images/vote-arrow-up.png");
+ $(getQuestionVoteDownButton()).attr("src", $.i18n._("/") + "content/images/vote-arrow-down.png");
+ }
+ else{
+ $(getAnswerVoteUpButton(postId)).attr("src", $.i18n._("/") + "content/images/vote-arrow-up.png");
+ $(getAnswerVoteDownButton(postId)).attr("src", $.i18n._("/") + "content/images/vote-arrow-down.png");
+=======
+ $(getQuestionVoteUpButton()).attr("src", scriptUrl + "content/images/vote-arrow-up.png");
+ $(getQuestionVoteDownButton()).attr("src", scriptUrl + "content/images/vote-arrow-down.png");
}
else{
- $(getAnswerVoteUpButton(postId)).attr("src", "/content/images/vote-arrow-up.png");
- $(getAnswerVoteDownButton(postId)).attr("src", "/content/images/vote-arrow-down.png");
+ $(getAnswerVoteUpButton(postId)).attr("src", scriptUrl + "content/images/vote-arrow-up.png");
+ $(getAnswerVoteDownButton(postId)).attr("src", scriptUrl + "content/images/vote-arrow-down.png");
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
}
}
};
@@ -182,42 +197,42 @@ var Vote = function(){
if(questionAuthorId == currentUserId){
var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
$(acceptedButtons).unbind('click').click(function(event){
- Vote.accept($(event.target))
+ Vote.accept($(event.target));
});
}
// set favorite question
var favoriteButton = getFavoriteButton();
favoriteButton.unbind('click').click(function(event){
- Vote.favorite($(event.target))
+ Vote.favorite($(event.target));
});
// question vote up
var questionVoteUpButton = getQuestionVoteUpButton();
questionVoteUpButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.questionUpVote)
+ Vote.vote($(event.target), VoteType.questionUpVote);
});
var questionVoteDownButton = getQuestionVoteDownButton();
questionVoteDownButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.questionDownVote)
+ Vote.vote($(event.target), VoteType.questionDownVote);
});
var answerVoteUpButton = getAnswerVoteUpButtons();
answerVoteUpButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.answerUpVote)
+ Vote.vote($(event.target), VoteType.answerUpVote);
});
var answerVoteDownButton = getAnswerVoteDownButtons();
answerVoteDownButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.answerDownVote)
+ Vote.vote($(event.target), VoteType.answerDownVote);
});
getOffensiveQuestionFlag().unbind('click').click(function(event){
- Vote.offensive(this, VoteType.offensiveQuestion)
+ Vote.offensive(this, VoteType.offensiveQuestion);
});
getOffensiveAnswerFlags().unbind('click').click(function(event){
- Vote.offensive(this, VoteType.offensiveAnswer)
+ Vote.offensive(this, VoteType.offensiveAnswer);
});
getremoveQuestionLink().unbind('click').click(function(event){
@@ -234,7 +249,7 @@ var Vote = function(){
});
getremoveAnswersLinks().unbind('click').click(function(event){
- Vote.remove(this, VoteType.removeAnswer)
+ Vote.remove(this, VoteType.removeAnswer);
});
};
@@ -243,14 +258,18 @@ var Vote = function(){
type: "POST",
cache: false,
dataType: "json",
- url: "/" + $.i18n._("questions/") + questionId + "/" + $.i18n._("vote/"),
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ url: $.i18n._("/") + $.i18n._("questions/") + questionId + "/" + $.i18n._("vote/"),
+=======
+ url: scriptUrl + $.i18n._("questions/") + questionId + "/" + $.i18n._("vote/"),
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
data: { "type": voteType, "postId": postId },
error: handleFail,
success: function(data){callback(object, voteType, data)}});
};
var handleFail = function(xhr, msg){
- alert("Callback invoke error: " + msg)
+ alert("Callback invoke error: " + msg);
};
// callback function for Accept Answer action
@@ -262,19 +281,31 @@ var Vote = function(){
showMessage(object, acceptOwnAnswerMessage);
}
else if(data.status == "1"){
- object.attr("src", "/content/images/vote-accepted.png");
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ object.attr("src", $.i18n._("/") + "content/images/vote-accepted.png");
+=======
+ object.attr("src", scriptUrl + "content/images/vote-accepted.png");
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
$("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
$("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
}
else if(data.success == "1"){
var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
- $(acceptedButtons).attr("src", "/content/images/vote-accepted.png");
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ $(acceptedButtons).attr("src", $.i18n._("/") + "content/images/vote-accepted.png");
+=======
+ $(acceptedButtons).attr("src", scriptUrl + "content/images/vote-accepted.png");
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
var answers = ("div[id^="+answerContainerIdPrefix +"]");
$(answers).removeClass("accepted-answer");
var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
$(commentLinks).removeClass("comment-link-accepted");
- object.attr("src", "/content/images/vote-accepted-on.png");
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ object.attr("src", $.i18n._("/") + "content/images/vote-accepted-on.png");
+=======
+ object.attr("src", scriptUrl + "content/images/vote-accepted-on.png");
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
$("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
$("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
}
@@ -288,7 +319,11 @@ var Vote = function(){
showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
}
else if(data.status == "1"){
- object.attr("src", "/content/images/vote-favorite-off.png");
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ object.attr("src", $.i18n._("/") + "content/images/vote-favorite-off.png");
+=======
+ object.attr("src", scriptUrl + "content/images/vote-favorite-off.png");
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
var fav = getFavoriteNumber();
fav.removeClass("my-favorite-number");
if(data.count == 0)
@@ -296,7 +331,11 @@ var Vote = function(){
fav.text(data.count);
}
else if(data.success == "1"){
- object.attr("src", "/content/images/vote-favorite-on.png");
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ object.attr("src", $.i18n._("/") + "/content/images/vote-favorite-on.png");
+=======
+ object.attr("src", scriptUrl + "content/images/vote-favorite-on.png");
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
var fav = getFavoriteNumber();
fav.text(data.count);
fav.addClass("my-favorite-number");
@@ -310,69 +349,79 @@ var Vote = function(){
if(data.allowed == "0" && data.success == "0"){
showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
}
- else if(data.allowed == "-3"){
+ else if (data.allowed == "-3"){
showMessage(object, voteRequiredMoreVotes);
}
- else if(data.allowed == "-2"){
- if(voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote){
+ else if (data.allowed == "-2"){
+ if (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote){
showMessage(object, upVoteRequiredScoreMessage);
}
- else if(voteType == VoteType.questionDownVote || voteType == VoteType.answerDownVote){
+ else if (voteType == VoteType.questionDownVote || voteType == VoteType.answerDownVote){
showMessage(object, downVoteRequiredScoreMessage);
}
}
- else if(data.allowed == "-1"){
+ else if (data.allowed == "-1"){
showMessage(object, voteOwnDeniedMessage);
}
- else if(data.status == "2"){
+ else if (data.status == "2"){
showMessage(object, voteDenyCancelMessage);
}
- else if(data.status == "1"){
+ else if (data.status == "1"){
setVoteImage(voteType, true, object);
setVoteNumber(object, data.count);
}
- else if(data.success == "1"){
+ else if (data.success == "1"){
setVoteImage(voteType, false, object);
setVoteNumber(object, data.count);
- if(data.message.length > 0)
+ 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"){
+ if (data.allowed == "0" && data.success == "0"){
showMessage(object, offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
}
- else if(data.allowed == "-3"){
+ else if (data.allowed == "-3"){
showMessage(object, offensiveNoFlagsLeftMessage);
}
- else if(data.allowed == "-2"){
+ else if (data.allowed == "-2"){
showMessage(object, offensiveNoPermissionMessage);
}
- else if(data.status == "1"){
+ else if (data.status == "1"){
showMessage(object, offensiveTwiceMessage);
}
- else if(data.success == "1"){
+ 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"){
+ 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);
- }
+ if (voteType == VoteType.removeQuestion){
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ window.location.href = $.i18n._("/") + $.i18n._("questions/");
+=======
+ window.location.href = scriptUrl + $.i18n._("questions/");
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
+ }
+ else {
+ if (removeActionType == 'delete'){
+ postNode.addClass('deleted');
+ postRemoveLink.innerHTML = $.i18n._('undelete');
+ showMessage(object, deletedMessage);
+ }
+ else if (removeActionType == 'undelete') {
+ postNode.removeClass('deleted');
+ postRemoveLink.innerHTML = $.i18n._('delete');
+ showMessage(object, recoveredMessage);
+ }
+ }
}
};
@@ -391,7 +440,7 @@ var Vote = function(){
},
favorite: function(object){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
return false;
}
@@ -399,14 +448,14 @@ var Vote = function(){
},
vote: function(object, voteType){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
return false;
}
- if(voteType == VoteType.answerUpVote){
+ if (voteType == VoteType.answerUpVote){
postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length);
}
- else if(voteType == VoteType.answerDownVote){
+ else if (voteType == VoteType.answerDownVote){
postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length);
}
@@ -414,39 +463,43 @@ var Vote = function(){
},
offensive: function(object, voteType){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
showMessage($(object), offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
return false;
}
- if(confirm(offensiveConfirmation)){
+ 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"){
+ 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';
- }
- }
+ bits = object.id.split('-');
+ postId = bits.pop();/* this seems to be used within submit! */
+ postType = bits.shift();
+
+ var do_proceed = false;
+ if (postType == 'answer'){
+ postNode = $('#answer-container-' + postId);
+ postRemoveLink = object;
+ if (postNode.hasClass('deleted')){
+ removeActionType = 'undelete';
+ do_proceed = true;
+ }
+ else {
+ removeActionType = 'delete';
+ do_proceed = confirm(removeConfirmation);
+ }
+ }
+ else {
+ do_proceed = confirm(removeConfirmation);
+ }
+ if (do_proceed) {
submit($(object), voteType, callback_remove);
-
-
}
}
}
@@ -457,21 +510,24 @@ var Vote = function(){
function createComments(type) {
var objectType = type;
var jDivInit = function(id) {
- return $("#comments-" + objectType + '-' + id);
+ return $("#comments-container-" + objectType + '-' + id);
};
var appendLoaderImg = function(id) {
- appendLoader("#comments-" + objectType + '-' + id + " div.comments");
+ appendLoader("#comments-container-" + objectType + '-' + id);
};
- var canPostComments = function(id, jDiv) {
- var jHidden = jDiv.siblings("#can-post-comments-" + objectType + '-' + id);
+ var canPostComments = function(id) {
+ var jHidden = $("#can-post-comments-" + objectType + '-' + id);
return jHidden.val().toLowerCase() == "true";
};
- var renderForm = function(id, jDiv) {
+ var renderForm = function(id) {
var formId = "form-comments-" + objectType + "-" + id;
- if (canPostComments(id, jDiv)) {
+ var jDiv = $('#comments-link-' + objectType + "-" + id).parent();
+ $(jDiv).css('background','none');
+ $(jDiv).css('padding-left',0);
+ if (canPostComments(id)) {
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)" ';
@@ -490,64 +546,96 @@ function createComments(type) {
else {
var divId = "comments-rep-needed-" + objectType + '-' + id;
if (jDiv.find("#" + divId).length == 0) {
- jDiv.append('<div id="' + divId + '" style="color:red">'
+ jDiv.append('<p id="' + divId + '" class="comment">'
+ $.i18n._('to comment, need') + ' ' +
- + repNeededForComments + ' ' + $.i18n._('community reputation points')
- + '<a href="/faq" class="comment-user">' + $.i18n._('please see') + 'faq</a></span>');
+ + repNeededForComments + ' ' + $.i18n._('community karma points')
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ + '<a href="' + $.i18n._('/') + $.i18n._('faq/') + '" class="comment-user">'
+=======
+ + '<a href="' + scriptUrl + $.i18n._('faq/') + '" class="comment-user">'
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
+ + $.i18n._('please see') + 'faq</a></span></p>');
}
}
};
var getComments = function(id, jDiv) {
- appendLoaderImg(id);
- $.getJSON("/" + objectType + "s/" + id + "/comments/", function(json) { showComments(id, json); });
+ //appendLoaderImg(id);
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ $.getJSON($.i18n._("/") + objectType + "s/" + id + "/" + $.i18n._("comments/")
+=======
+ $.getJSON(scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/")
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
+ , function(json) { showComments(id, json); });
};
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..
-
+ //jDiv = jDiv.find("div.comments"); // this div should contain any fetched comments..
+ //jDiv.find("div[id^='comment-" + objectType + "-'" + "]").remove(); // clean previous calls..
+ jDiv.children().remove();
removeLoader();
-
if (json && json.length > 0) {
for (var i = 0; i < json.length; i++)
renderComment(jDiv, json[i]);
-
jDiv.children().show();
}
};
+ var renderDeleteCommentIcon = function(post_id, delete_url){
+ if (canPostComments(post_id)){
+ var html = '';
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ var img = $.i18n._("/") + "content/images/close-small.png";
+ var imgHover = $.i18n._("/") + "content/images/close-small-hover.png";
+=======
+ var img = scriptUrl + "content/images/close-small.png";
+ var imgHover = scriptUrl + "content/images/close-small-hover.png";
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
+ html += '<img class="delete-icon" onclick="' + objectType + 'Comments.deleteComment($(this), ' + post_id + ', \'' + delete_url + '\')" src="' + img;
+ html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img
+ html += '\')" title="' + $.i18n._('delete this comment') + '" />';
+ return html;
+ }
+ else{
+ return '';
+ }
+ }
+
// {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
var renderComment = function(jDiv, json) {
- 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';
+ var html = '<p id="comment-' + json.id + '" class="comment" style="display:none">' + json.text;
+ html += json.user_url ? ' - <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>';
+ html += ' (' + json.comment_age + ')';
+
+ if (json.delete_url){
+ html += renderDeleteCommentIcon(json.object_id, json.delete_url);
+ }
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>';
+ html += '</p>';
jDiv.append(html);
};
var postComment = function(id, formId) {
- appendLoaderImg(id);
+ //appendLoaderImg(id);
var formSelector = "#" + formId;
var textarea = $(formSelector + " textarea");
+ //todo fix url translations!!!
$.ajax({
type: "POST",
- url: "/" + objectType + "s/" + id + "/comments/",
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ url: $.i18n._("/") + objectType + "s/" + id + "/" + $.i18n._("comments/"),
+=======
+ url: scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/"),
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
dataType: "json",
data: { comment: textarea.val() },
success: function(json) {
@@ -569,18 +657,64 @@ function createComments(type) {
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)); });
+ $("a[id^='comments-link-" + objectType + "-" + "']").unbind("click").click(function() {
+ commentsFactory[objectType].show($(this).attr("id").substr(("comments-link-" + objectType + "-").length));
+ });
+
+ var cBox = $("[id^='comments-container-" + objectType + "']");
+ cBox.each( function(i){
+ var post_id = $(this).attr('id').replace('comments-container-' + objectType + '-', '');
+ $(this).children().each(
+ function(i){
+ var comment_id = $(this).attr('id').replace('comment-','');
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ var delete_url = $.i18n._('/') + objectType + 's/' + post_id + '/'
+=======
+ var delete_url = scriptUrl + objectType + 's/' + post_id + '/'
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
+ + $.i18n._('comments/') + comment_id + '/' + $.i18n._('delete/');
+ var html = $(this).html();
+ var CommentsClass;
+ if (objectType == 'question'){
+ CommentsClass = questionComments;
+ }
+ else if (objectType == 'answer') {
+ CommentsClass = answerComments;
+ }
+ var delete_icon = $(this).find('img.delete-icon');
+ delete_icon.click(function(){CommentsClass.deleteComment($(this),comment_id,delete_url);});
+ delete_icon.unbind('mouseover').bind('mouseover',
+ function(){
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ $(this).attr('src',$.i18n._('/') + 'content/images/close-small-hover.png');
+=======
+ $(this).attr('src',scriptUrl + 'content/images/close-small-hover.png');
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
+ }
+ );
+ delete_icon.unbind('mouseout').bind('mouseout',
+ function(){
+<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
+ $(this).attr('src',$.i18n._('/') + 'content/images/close-small.png');
+=======
+ $(this).attr('src',scriptUrl + 'content/images/close-small.png');
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
+ }
+ );
+ }
+ );
+ });
},
show: function(id) {
var jDiv = jDivInit(id);
getComments(id, jDiv);
- renderForm(id, jDiv);
+ renderForm(id);
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'));
+
+ var link = $('#comments-link-' + objectType + '-' + id);
+ if (canPostComments(id)) link.parent().find("textarea").get(0).focus();
+ link.remove();
},
hide: function(id) {
@@ -596,9 +730,9 @@ function createComments(type) {
deleteComment: function(jImg, id, deleteUrl) {
if (confirm($.i18n._('confirm delete comment'))) {
jImg.hide();
- appendLoaderImg(id);
$.post(deleteUrl, { dataNeeded: "forIIS7" }, function(json) {
- showComments(id, json);
+ var par = jImg.parent();
+ par.remove();
}, "json");
}
},
diff --git a/templates/content/js/com.cnprog.tag_selector.js b/templates/content/js/com.cnprog.tag_selector.js
new file mode 100644
index 00000000..f6c16c9c
--- /dev/null
+++ b/templates/content/js/com.cnprog.tag_selector.js
@@ -0,0 +1,168 @@
+function pickedTags(){
+
+ var sendAjax = function(tagname, reason, action, callback){
+ url = scriptUrl;
+ if (action == 'add'){
+ url += $.i18n._('mark-tag/');
+ if (reason == 'good'){
+ url += $.i18n._('interesting/');
+ }
+ else {
+ url += $.i18n._('ignored/');
+ }
+ }
+ else {
+ url += $.i18n._('unmark-tag/');
+ }
+ url = url + tagname + '/';
+
+ call_settings = {
+ type:'POST',
+ url:url
+ }
+ if (callback != false){
+ call_settings['success'] = callback;
+ }
+ $.ajax(call_settings);
+ }
+
+
+ var unpickTag = function(from_target ,tagname, reason, send_ajax){
+ //send ajax request to delete tag
+ var deleteTagLocally = function(){
+ from_target[tagname].remove();
+ delete from_target[tagname];
+ }
+ if (send_ajax){
+ sendAjax(tagname,reason,'remove',deleteTagLocally);
+ }
+ else {
+ deleteTagLocally();
+ }
+
+ }
+
+ var setupTagDeleteEvents = function(obj,tag_store,tagname,reason,send_ajax){
+ obj.unbind('mouseover').bind('mouseover', function(){
+ $(this).attr('src', scriptUrl + 'content/images/close-small-hover.png');
+ });
+ obj.unbind('mouseout').bind('mouseout', function(){
+ $(this).attr('src', scriptUrl + 'content/images/close-small-dark.png');
+ });
+ obj.click( function(){
+ unpickTag(tag_store,tagname,reason,send_ajax);
+ });
+ }
+
+ var handlePickedTag = function(obj,reason){
+ var tagname = $.trim($(obj).prev().attr('value'));
+ to_target = interestingTags;
+ from_target = ignoredTags;
+ if (reason == 'bad'){
+ to_target = ignoredTags;
+ from_target = interestingTags;
+ to_tag_container = $('div .tags.ignored');
+ }
+ else if (reason != 'good'){
+ return;
+ }
+ else {
+ to_tag_container = $('div .tags.interesting');
+ }
+
+ if (tagname in from_target){
+ unpickTag(from_target,tagname,reason,false);
+ }
+
+ if (!(tagname in to_target)){
+ //send ajax request to pick this tag
+
+ sendAjax(tagname,reason,'add',function(){
+ new_tag = $('<span></span>');
+ new_tag.addClass('deletable-tag');
+ tag_link = $('<a></a>');
+ tag_link.attr('rel','tag');
+ tag_link.attr('href', scriptUrl + $.i18n._('tags/') + tagname);
+ tag_link.html(tagname);
+ del_link = $('<img></img>');
+ del_link.addClass('delete-icon');
+ del_link.attr('src', scriptUrl + 'content/images/close-small-dark.png');
+
+ setupTagDeleteEvents(del_link, to_target, tagname, reason, true);
+
+ new_tag.append(tag_link);
+ new_tag.append(del_link);
+ to_tag_container.append(new_tag);
+
+ to_target[tagname] = new_tag;
+ });
+ }
+ }
+
+ var collectPickedTags = function(){
+ var good_prefix = 'interesting-tag-';
+ var bad_prefix = 'ignored-tag-';
+ var good_re = RegExp('^' + good_prefix);
+ var bad_re = RegExp('^' + bad_prefix);
+ interestingTags = {};
+ ignoredTags = {};
+ $('.deletable-tag').each(
+ function(i,item){
+ item_id = $(item).attr('id')
+ if (good_re.test(item_id)){
+ tag_name = item_id.replace(good_prefix,'');
+ tag_store = interestingTags;
+ reason = 'good';
+ }
+ else if (bad_re.test(item_id)){
+ tag_name = item_id.replace(bad_prefix,'');
+ tag_store = ignoredTags;
+ reason = 'bad';
+ }
+ else {
+ return;
+ }
+ tag_store[tag_name] = $(item);
+ setupTagDeleteEvents($(item).find('img'),tag_store,tag_name,reason,true)
+ }
+ );
+ }
+
+ var setupHideIgnoredQuestionsControl = function(){
+ $('#hideIgnoredTagsCb').unbind('click').click(function(){
+ $.ajax({
+ type: 'POST',
+ dataType: 'json',
+ cache: false,
+ url: scriptUrl + $.i18n._('command/'),
+ data: {command:'toggle-ignored-questions'}
+ });
+ });
+ }
+ return {
+ init: function(){
+ collectPickedTags();
+ setupHideIgnoredQuestionsControl();
+ $("#interestingTagInput, #ignoredTagInput").autocomplete(tags, {
+ minChars: 1,
+ matchContains: true,
+ max: 20,
+ multiple: true,
+ multipleSeparator: " ",
+ formatItem: function(row, i, max) {
+ return row.n + " ("+ row.c +")";
+ },
+ formatResult: function(row, i, max){
+ return row.n;
+ }
+
+ });
+ $("#interestingTagAdd").click(function(){handlePickedTag(this,'good')});
+ $("#ignoredTagAdd").click(function(){handlePickedTag(this,'bad')});
+ }
+ };
+}
+
+$(document).ready( function(){
+ pickedTags().init();
+});
diff --git a/templates/content/js/com.cnprog.utils.js b/templates/content/js/com.cnprog.utils.js
index e271ed78..5c0c4a27 100644
--- a/templates/content/js/com.cnprog.utils.js
+++ b/templates/content/js/com.cnprog.utils.js
@@ -23,7 +23,12 @@ var notify = function() {
},
close: function(doPostback) {
if (doPostback) {
- $.post("/messages/markread/", { formdata: "required" });
+<<<<<<< HEAD:templates/content/js/com.cnprog.utils.js
+ $.post($.i18n._("/") + $.i18n._("messages/") +
+=======
+ $.post(scriptUrl + $.i18n._("messages/") +
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.utils.js
+ $.i18n._("markread/"), { formdata: "required" });
}
$(".notify").fadeOut("fast");
$("body").css("margin-top", "0");
@@ -35,7 +40,11 @@ var notify = function() {
function appendLoader(containerSelector) {
$(containerSelector).append('<img class="ajax-loader" '
- +'src="/content/images/indicator.gif" title="'
+<<<<<<< HEAD:templates/content/js/com.cnprog.utils.js
+ +'src="' + $.i18n._('/') + 'content/images/indicator.gif" title="'
+=======
+ +'src="' + scriptUrl + 'content/images/indicator.gif" title="'
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.utils.js
+$.i18n._('loading...')
+'" alt="'
+$.i18n._('loading...')
@@ -103,7 +112,7 @@ var CPValidator = function(){
return {
tags: {
required: " " + $.i18n._('tags cannot be empty'),
- maxlength: " " + $.i18n._('tablimits info'),
+ maxlength: " " + $.i18n._('tablimits info')
},
text: {
required: " " + $.i18n._('content cannot be empty'),
diff --git a/templates/content/js/compress.bat b/templates/content/js/compress.bat
index 41e1882a..5b2673cf 100644
--- a/templates/content/js/compress.bat
+++ b/templates/content/js/compress.bat
@@ -2,5 +2,4 @@
#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 wmd\showdown.js -o wmd\showdown-min.js
#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 com.cnprog.post.js -o com.cnprog.post.pack.js
java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 se_hilite_src.js -o se_hilite.js
-
-pause \ No newline at end of file
+pause
diff --git a/templates/content/js/flot-build.bat b/templates/content/js/flot-build.bat
index fc715e3a..f9f32cb7 100644
--- a/templates/content/js/flot-build.bat
+++ b/templates/content/js/flot-build.bat
@@ -1,3 +1,3 @@
java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 jquery.flot.js -o jquery.flot.pack.js
-pause \ No newline at end of file
+pause
diff --git a/templates/content/js/jquery.form.js b/templates/content/js/jquery.form.js
new file mode 100644
index 00000000..443114fd
--- /dev/null
+++ b/templates/content/js/jquery.form.js
@@ -0,0 +1,654 @@
+/*
+ * jQuery Form Plugin
+ * version: 2.33 (22-SEP-2009)
+ * @requires jQuery v1.2.6 or later
+ *
+ * Examples and documentation at: http://malsup.com/jquery/form/
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ */
+;(function($) {
+
+/*
+ Usage Note:
+ -----------
+ Do not use both ajaxSubmit and ajaxForm on the same form. These
+ functions are intended to be exclusive. Use ajaxSubmit if you want
+ to bind your own submit handler to the form. For example,
+
+ $(document).ready(function() {
+ $('#myForm').bind('submit', function() {
+ $(this).ajaxSubmit({
+ target: '#output'
+ });
+ return false; // <-- important!
+ });
+ });
+
+ Use ajaxForm when you want the plugin to manage all the event binding
+ for you. For example,
+
+ $(document).ready(function() {
+ $('#myForm').ajaxForm({
+ target: '#output'
+ });
+ });
+
+ When using ajaxForm, the ajaxSubmit function will be invoked for you
+ at the appropriate time.
+*/
+
+/**
+ * ajaxSubmit() provides a mechanism for immediately submitting
+ * an HTML form using AJAX.
+ */
+$.fn.ajaxSubmit = function(options) {
+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
+ if (!this.length) {
+ log('ajaxSubmit: skipping submit process - no element selected');
+ return this;
+ }
+
+ if (typeof options == 'function')
+ options = { success: options };
+
+ var url = $.trim(this.attr('action'));
+ if (url) {
+ // clean url (don't include hash vaue)
+ url = (url.match(/^([^#]+)/)||[])[1];
+ }
+ url = url || window.location.href || '';
+
+ options = $.extend({
+ url: url,
+ type: this.attr('method') || 'GET'
+ }, options || {});
+
+ // hook for manipulating the form data before it is extracted;
+ // convenient for use with rich editors like tinyMCE or FCKEditor
+ var veto = {};
+ this.trigger('form-pre-serialize', [this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
+ return this;
+ }
+
+ // provide opportunity to alter form data before it is serialized
+ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSerialize callback');
+ return this;
+ }
+
+ var a = this.formToArray(options.semantic);
+ if (options.data) {
+ options.extraData = options.data;
+ for (var n in options.data) {
+ if(options.data[n] instanceof Array) {
+ for (var k in options.data[n])
+ a.push( { name: n, value: options.data[n][k] } );
+ }
+ else
+ a.push( { name: n, value: options.data[n] } );
+ }
+ }
+
+ // give pre-submit callback an opportunity to abort the submit
+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSubmit callback');
+ return this;
+ }
+
+ // fire vetoable 'validate' event
+ this.trigger('form-submit-validate', [a, this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
+ return this;
+ }
+
+ var q = $.param(a);
+
+ if (options.type.toUpperCase() == 'GET') {
+ options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
+ options.data = null; // data is null for 'get'
+ }
+ else
+ options.data = q; // data is the query string for 'post'
+
+ var $form = this, callbacks = [];
+ if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
+ if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
+
+ // perform a load on the target only if dataType is not provided
+ if (!options.dataType && options.target) {
+ var oldSuccess = options.success || function(){};
+ callbacks.push(function(data) {
+ $(options.target).html(data).each(oldSuccess, arguments);
+ });
+ }
+ else if (options.success)
+ callbacks.push(options.success);
+
+ options.success = function(data, status) {
+ for (var i=0, max=callbacks.length; i < max; i++)
+ callbacks[i].apply(options, [data, status, $form]);
+ };
+
+ // are there files to upload?
+ var files = $('input:file', this).fieldValue();
+ var found = false;
+ for (var j=0; j < files.length; j++)
+ if (files[j])
+ found = true;
+
+ var multipart = false;
+// var mp = 'multipart/form-data';
+// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
+
+ // options.iframe allows user to force iframe mode
+ if (options.iframe || found || multipart) {
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+ if (options.closeKeepAlive)
+ $.get(options.closeKeepAlive, fileUpload);
+ else
+ fileUpload();
+ }
+ else{
+ $.ajax(options);
+ }
+
+ // fire 'notify' event
+ this.trigger('form-submit-notify', [this, options]);
+ return this;
+
+
+ // private function for handling file uploads (hat tip to YAHOO!)
+ function fileUpload() {
+ var form = $form[0];
+
+ if ($(':input[name=submit]', form).length) {
+ alert('Error: Form elements must not be named "submit".');
+ return;
+ }
+
+ var opts = $.extend({}, $.ajaxSettings, options);
+ var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
+
+ var id = 'jqFormIO' + (new Date().getTime());
+ var $io = $('<iframe id="' + id + '" name="' + id + '" src="about:blank" />');
+ var io = $io[0];
+
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+
+ var xhr = { // mock object
+ aborted: 0,
+ responseText: null,
+ responseXML: null,
+ status: 0,
+ statusText: 'n/a',
+ getAllResponseHeaders: function() {},
+ getResponseHeader: function() {},
+ setRequestHeader: function() {},
+ abort: function() {
+ this.aborted = 1;
+ $io.attr('src','about:blank'); // abort op in progress
+ }
+ };
+
+ var g = opts.global;
+ // trigger ajax global events so that activity/block indicators work like normal
+ if (g && ! $.active++) $.event.trigger("ajaxStart");
+ if (g) $.event.trigger("ajaxSend", [xhr, opts]);
+
+ if (s.beforeSend && s.beforeSend(xhr, s) === false) {
+ s.global && $.active--;
+ return;
+ }
+ if (xhr.aborted)
+ return;
+
+ var cbInvoked = 0;
+ var timedOut = 0;
+
+ // add submitting element to data if we know it
+ var sub = form.clk;
+ if (sub) {
+ var n = sub.name;
+ if (n && !sub.disabled) {
+ options.extraData = options.extraData || {};
+ options.extraData[n] = sub.value;
+ if (sub.type == "image") {
+ options.extraData[name+'.x'] = form.clk_x;
+ options.extraData[name+'.y'] = form.clk_y;
+ }
+ }
+ }
+
+ // take a breath so that pending repaints get some cpu time before the upload starts
+ setTimeout(function() {
+ // make sure form attrs are set
+ var t = $form.attr('target'), a = $form.attr('action');
+
+ // update form attrs in IE friendly way
+ form.setAttribute('target',id);
+ if (form.getAttribute('method') != 'POST')
+ form.setAttribute('method', 'POST');
+ if (form.getAttribute('action') != opts.url)
+ form.setAttribute('action', opts.url);
+
+ // ie borks in some cases when setting encoding
+ if (! options.skipEncodingOverride) {
+ $form.attr({
+ encoding: 'multipart/form-data',
+ enctype: 'multipart/form-data'
+ });
+ }
+
+ // support timout
+ if (opts.timeout)
+ setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
+
+ // add "extra" data to form if provided in options
+ var extraInputs = [];
+ try {
+ if (options.extraData)
+ for (var n in options.extraData)
+ extraInputs.push(
+ $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
+ .appendTo(form)[0]);
+
+ // add iframe to doc and submit the form
+ $io.appendTo('body');
+ io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
+ form.submit();
+ }
+ finally {
+ // reset attrs and remove "extra" input elements
+ form.setAttribute('action',a);
+ t ? form.setAttribute('target', t) : $form.removeAttr('target');
+ $(extraInputs).remove();
+ }
+ }, 10);
+
+ var domCheckCount = 50;
+
+ function cb() {
+ if (cbInvoked++) return;
+
+ io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
+
+ var ok = true;
+ try {
+ if (timedOut) throw 'timeout';
+ // extract the server response from the iframe
+ var data, doc;
+
+ doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
+
+ var isXml = opts.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
+ log('isXml='+isXml);
+ if (!isXml && (doc.body == null || doc.body.innerHTML == '')) {
+ if (--domCheckCount) {
+ // in some browsers (Opera) the iframe DOM is not always traversable when
+ // the onload callback fires, so we loop a bit to accommodate
+ cbInvoked = 0;
+ setTimeout(cb, 100);
+ return;
+ }
+ log('Could not access iframe DOM after 50 tries.');
+ return;
+ }
+
+ xhr.responseText = doc.body ? doc.body.innerHTML : null;
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+ xhr.getResponseHeader = function(header){
+ var headers = {'content-type': opts.dataType};
+ return headers[header];
+ };
+
+ if (opts.dataType == 'json' || opts.dataType == 'script') {
+ // see if user embedded response in textarea
+ var ta = doc.getElementsByTagName('textarea')[0];
+ if (ta)
+ xhr.responseText = ta.value;
+ else {
+ // account for browsers injecting pre around json response
+ var pre = doc.getElementsByTagName('pre')[0];
+ if (pre)
+ xhr.responseText = pre.innerHTML;
+ }
+ }
+ else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
+ xhr.responseXML = toXml(xhr.responseText);
+ }
+ data = $.httpData(xhr, opts.dataType);
+ }
+ catch(e){
+ ok = false;
+ $.handleError(opts, xhr, 'error', e);
+ }
+
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+ if (ok) {
+ opts.success(data, 'success');
+ if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
+ }
+ if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
+ if (g && ! --$.active) $.event.trigger("ajaxStop");
+ if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
+
+ // clean up
+ setTimeout(function() {
+ $io.remove();
+ xhr.responseXML = null;
+ }, 100);
+ };
+
+ function toXml(s, doc) {
+ if (window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML(s);
+ }
+ else
+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
+ return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
+ };
+ };
+};
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
+ * is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ * used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */
+$.fn.ajaxForm = function(options) {
+ return this.ajaxFormUnbind().bind('submit.form-plugin', function() {
+ $(this).ajaxSubmit(options);
+ return false;
+ }).bind('click.form-plugin', function(e) {
+ var $el = $(e.target);
+ if (!($el.is(":submit,input:image"))) {
+ return;
+ }
+ var form = this;
+ form.clk = e.target;
+ if (e.target.type == 'image') {
+ if (e.offsetX != undefined) {
+ form.clk_x = e.offsetX;
+ form.clk_y = e.offsetY;
+ } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
+ var offset = $el.offset();
+ form.clk_x = e.pageX - offset.left;
+ form.clk_y = e.pageY - offset.top;
+ } else {
+ form.clk_x = e.pageX - e.target.offsetLeft;
+ form.clk_y = e.pageY - e.target.offsetTop;
+ }
+ }
+ // clear form vars
+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
+ });
+};
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+ return this.unbind('submit.form-plugin click.form-plugin');
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property. An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic) {
+ var a = [];
+ if (this.length == 0) return a;
+
+ var form = this[0];
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
+ if (!els) return a;
+ for(var i=0, max=els.length; i < max; i++) {
+ var el = els[i];
+ var n = el.name;
+ if (!n) continue;
+
+ if (semantic && form.clk && el.type == "image") {
+ // handle image inputs on the fly when semantic == true
+ if(!el.disabled && form.clk == el) {
+ a.push({name: n, value: $(el).val()});
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ continue;
+ }
+
+ var v = $.fieldValue(el, true);
+ if (v && v.constructor == Array) {
+ for(var j=0, jmax=v.length; j < jmax; j++)
+ a.push({name: n, value: v[j]});
+ }
+ else if (v !== null && typeof v != 'undefined')
+ a.push({name: n, value: v});
+ }
+
+ if (!semantic && form.clk) {
+ // input type=='image' are not found in elements array! handle it here
+ var $input = $(form.clk), input = $input[0], n = input.name;
+ if (n && !input.disabled && input.type == 'image') {
+ a.push({name: n, value: $input.val()});
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ }
+ return a;
+};
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&amp;name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+ //hand off to jQuery.param for proper encoding
+ return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&amp;name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+ var a = [];
+ this.each(function() {
+ var n = this.name;
+ if (!n) return;
+ var v = $.fieldValue(this, successful);
+ if (v && v.constructor == Array) {
+ for (var i=0,max=v.length; i < max; i++)
+ a.push({name: n, value: v[i]});
+ }
+ else if (v !== null && typeof v != 'undefined')
+ a.push({name: this.name, value: v});
+ });
+ //hand off to jQuery.param for proper encoding
+ return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
+ *
+ * <form><fieldset>
+ * <input name="A" type="text" />
+ * <input name="A" type="text" />
+ * <input name="B" type="checkbox" value="B1" />
+ * <input name="B" type="checkbox" value="B2"/>
+ * <input name="C" type="radio" value="C1" />
+ * <input name="C" type="radio" value="C2" />
+ * </fieldset></form>
+ *
+ * var v = $(':text').fieldValue();
+ * // if no values are entered into the text inputs
+ * v == ['','']
+ * // if values entered into the text inputs are 'foo' and 'bar'
+ * v == ['foo','bar']
+ *
+ * var v = $(':checkbox').fieldValue();
+ * // if neither checkbox is checked
+ * v === undefined
+ * // if both checkboxes are checked
+ * v == ['B1', 'B2']
+ *
+ * var v = $(':radio').fieldValue();
+ * // if neither radio is checked
+ * v === undefined
+ * // if first radio is checked
+ * v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true. If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array. If no valid value can be determined the
+ * array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+ for (var val=[], i=0, max=this.length; i < max; i++) {
+ var el = this[i];
+ var v = $.fieldValue(el, successful);
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
+ continue;
+ v.constructor == Array ? $.merge(val, v) : val.push(v);
+ }
+ return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+ if (typeof successful == 'undefined') successful = true;
+
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+ tag == 'select' && el.selectedIndex == -1))
+ return null;
+
+ if (tag == 'select') {
+ var index = el.selectedIndex;
+ if (index < 0) return null;
+ var a = [], ops = el.options;
+ var one = (t == 'select-one');
+ var max = (one ? index+1 : ops.length);
+ for(var i=(one ? index : 0); i < max; i++) {
+ var op = ops[i];
+ if (op.selected) {
+ var v = op.value;
+ if (!v) // extra pain for IE...
+ v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
+ if (one) return v;
+ a.push(v);
+ }
+ }
+ return a;
+ }
+ return el.value;
+};
+
+/**
+ * Clears the form data. Takes the following actions on the form's input fields:
+ * - input text fields will have their 'value' property set to the empty string
+ * - select elements will have their 'selectedIndex' property set to -1
+ * - checkbox and radio inputs will have their 'checked' property set to false
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
+ * - button elements will *not* be effected
+ */
+$.fn.clearForm = function() {
+ return this.each(function() {
+ $('input,select,textarea', this).clearFields();
+ });
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function() {
+ return this.each(function() {
+ var t = this.type, tag = this.tagName.toLowerCase();
+ if (t == 'text' || t == 'password' || tag == 'textarea')
+ this.value = '';
+ else if (t == 'checkbox' || t == 'radio')
+ this.checked = false;
+ else if (tag == 'select')
+ this.selectedIndex = -1;
+ });
+};
+
+/**
+ * Resets the form data. Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+ return this.each(function() {
+ // guard against an input with the name of 'reset'
+ // note that IE reports the reset function as an 'object'
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
+ this.reset();
+ });
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) {
+ if (b == undefined) b = true;
+ return this.each(function() {
+ this.disabled = !b;
+ });
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.selected = function(select) {
+ if (select == undefined) select = true;
+ return this.each(function() {
+ var t = this.type;
+ if (t == 'checkbox' || t == 'radio')
+ this.checked = select;
+ else if (this.tagName.toLowerCase() == 'option') {
+ var $sel = $(this).parent('select');
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
+ // deselect all other options
+ $sel.find('option').selected(false);
+ }
+ this.selected = select;
+ }
+ });
+};
+
+// helper fn for console logging
+// set $.fn.ajaxSubmit.debug to true to enable debug logging
+function log() {
+ if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
+ window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
+};
+
+})(jQuery);
diff --git a/templates/content/js/wmd/wmd.js b/templates/content/js/wmd/wmd.js
index 7b611dba..2234250b 100644
--- a/templates/content/js/wmd/wmd.js
+++ b/templates/content/js/wmd/wmd.js
@@ -54,7 +54,11 @@ Attacklab.wmdBase = function(){
var uploadImageHTML ="<div>" + $.i18n._('upload image') + "</div>" +
"<input type=\"file\" name=\"file-upload\" id=\"file-upload\" size=\"26\" "+
"onchange=\"return ajaxFileUpload($('#image-url'));\"/><br>" +
- "<img id=\"loading\" src=\"/content/images/indicator.gif\" style=\"display:none;\"/>";
+<<<<<<< HEAD:templates/content/js/wmd/wmd.js
+ "<img id=\"loading\" src=\"" + $.i18n._("/") + "content/images/indicator.gif\" style=\"display:none;\"/>";
+=======
+ "<img id=\"loading\" src=\"" + scriptUrl + "content/images/indicator.gif\" style=\"display:none;\"/>";
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/wmd/wmd.js
// The default text that appears in the dialog input box when entering
// links.
diff --git a/templates/content/style/default.css b/templates/content/style/default.css
index 0221cc03..2bc185ad 100644
--- a/templates/content/style/default.css
+++ b/templates/content/style/default.css
@@ -7,9 +7,9 @@ Style sheet for cnprog.com
All rights reserved. 2008 CNPROG.COM
*/
-@import url(/content/style/jquery.autocomplete.css);
-@import url(/content/style/openid.css);
-@import url(/content/style/prettify.css);
+@import url(content/style/jquery.autocomplete.css);
+@import url(content/style/openid.css);
+@import url(content/style/prettify.css);
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, form, label, table, caption, tbody, tfoot, thead, tr, th, td
{
@@ -232,19 +232,19 @@ h4 {display:block;font-size:90%; font-family:Verdana;color:#ccc;}
}
#main-bar .golden{
- background:url(/content/images/bg_title_golden.gif) no-repeat;
+ background:url(../../images/bg_title_golden.gif) no-repeat;
}
#main-bar .pink{
- background:url(/content/images/bg_title_red.gif) no-repeat;
+ background:url(../../images/bg_title_red.gif) no-repeat;
}
#main-bar .orange{
- background:url(/content/images/bg_title_orange.gif) no-repeat;
+ background:url(../../images/bg_title_orange.gif) no-repeat;
}
#main-bar .green{
- background:url(/content/images/bg_title_green.gif) no-repeat;
+ background:url(../../images/bg_title_green.gif) no-repeat;
}
#tab{
@@ -906,7 +906,7 @@ h4 {display:block;font-size:90%; font-family:Verdana;color:#ccc;}
/* 2 textarea resizer styles */
div.grippie {
- background:#EEEEEE url(/content/images/grippie.png) no-repeat scroll center 2px;
+ background:#EEEEEE url(../../images/grippie.png) no-repeat scroll center 2px;
border-color:#DDDDDD;
border-style:solid;
border-width:0pt 1px 1px;
@@ -923,14 +923,14 @@ div.grippie {
}
.openid-input{
- background:url(/content/images/openid.gif) no-repeat;
+ background:url(../../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%;
+ background:url(../../images/openid.gif) no-repeat 0% 50%;
padding:5px 5px 5px 15px;
cursor:pointer;
font-family:Trebuchet MS;
@@ -941,7 +941,7 @@ div.grippie {
.openid-login-submit{
padding:6px;
- #padding:4px;
+ /*padding:4px;*/
cursor:pointer;
font-weight:bold;
font-size:120%;
@@ -990,7 +990,6 @@ div.grippie {
.item-right{
float:left;
-
}
.vote-number{
@@ -1609,7 +1608,9 @@ div.comments {
}
div.post-comments{
- width:585px
+ width:585px;
+ clear:both;
+ float:left;
}
form.post-comments textarea {
diff --git a/templates/content/style/jquery.autocomplete.css b/templates/content/style/jquery.autocomplete.css
index 7c3127d1..3bf2c2d9 100644
--- a/templates/content/style/jquery.autocomplete.css
+++ b/templates/content/style/jquery.autocomplete.css
@@ -36,7 +36,7 @@
}
.ac_loading {
- background: white url('/content/images/indicator.gif') right center no-repeat;
+ background: white url(../../images/indicator.gif) right center no-repeat;
}
.ac_odd {
diff --git a/templates/content/style/style.css b/templates/content/style/style.css
index 241d72da..cf35ff68 100644
--- a/templates/content/style/style.css
+++ b/templates/content/style/style.css
@@ -1,15 +1,16 @@
-@import url(/content/style/jquery.autocomplete.css);
-@import url(/content/style/openid.css);
-@import url(/content/style/prettify.css);
+@import url(jquery.autocomplete.css);
+@import url(openid.css);
+@import url(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;}
+label {vertical-align:middle;}
+hr {border:none;border-top: 1px dashed #ccccce;}
+input, select {vertical-align:middle;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;}
@@ -49,6 +50,12 @@ ol
margin-bottom: 1em;
padding-left:0px;
}
+td ul {
+ vertical-align:middle;
+}
+li input {
+ margin: 3px 3px 4px 3px;
+}
pre
{
font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
@@ -112,62 +119,129 @@ blockquote
#navBar .nav {margin:20px 0px 0px 16px;
/*letter-spacing:1px; */
}
-#navBar .nav a {color:#333333; background-color:#F9F7ED;
- border: 1px solid #aaaaaa;
- border-bottom: none;
+#navBar .nav a {color:#333333; background-color:#fff0e0;
+ /*border-left: 1px solid #eeeeec;
+ border-right: 1px solid #babdb6;
+ border-top: 1px solid #eeeeec;*/
+ border: 1px solid #888888;
+ 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; }
+#navBar .nav a.on {height:24px;line-height:28px;
+ border-bottom: 1px solid #a40000;
+ border-right:1px solid #820000;
+ border-top:1px solid #d40000;
+ border-left:1px solid #d40000;
+ /*background:#A31E39; */
+ background:#a40000;
+ color:#FFF; font-weight:600; text-decoration:none}
+#navBar .nav a.special {font-size:14px; color:#B02B2C; font-weight:bold; text-decoration:none; }
#navBar .nav a.special:hover {text-decoration:underline;}
#navBar .nav div.focus {float:right; padding-right:0px;}
/*搜索栏*/
-#searchBar {
- background-color:#9db2b1;/*#e9b96e;*/
- padding:5px 0 0 0;} /* #B02B2C */
+#searchBar {width:958px;
+ background-color:#888a85;/*#e9b96e;*/
+ border: 1px solid #aaaaaa;
+ padding:4px 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;
+#searchBar .options {padding:3px 0 3px 0;font-size:100%;color:#EEE;
/*letter-spacing:1px;*/
}
-#searchBar .options INPUT {margin-left:15px;}
+#searchBar .options INPUT {margin:0 3px 0 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 {
+ position:relative;
+ padding:3px 5px 5px 10px;
+ border-top:1px dashed #ccccce;
+ /*border-left:1px solid #ebebbe;
+ border-right:1px solid #b4b48e;
+ border-bottom:1px solid #b4b48e;*/
+ background: white;/* #f9f7ed;*/
+ /*margin:10px 0 10px 0;*/
+ /*background:url(../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 H2 {font-size:14px; font-weight:800; margin:8px auto;padding:0px;}
+#listA .qstA H2 a {color:333333/*#2e3436*/;font-size:15px;}
+#listA .qstA .stat {
+ position:absolute;
+ right:0px;
+ bottom:5px;
+ font-size:12px;
+ /*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 .stat .num {
+ font-family:sans-serif;
+ color:#a40000;
+ /*background:#eeeeec;
+ border: 1px solid #babdb6;*/
+ margin:0px;
+ font-size:17px;
+ font-weight:800;
+}
+#listA .qstA table {border-spacing:0px;}
+#listA .qstA table td {padding:0px;width:60px;text-align:center;}
+#listA .qstA .stat .unit {color:#777777;margin:0px}
+#listA .qstA .from {margin-top:5px; font-size:13px;color:#777777;}
+#listA .qstA .from .score {font-family:sans-serif;color:#555555;}
+#listA .qstA .date {margin-left:10px; color:#777777;}
+#listA .qstA .wiki {color:#763333;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 .author a{color:#444444; }
#listA .qstA .summary{margin-right:5px;}
-
+#question-table {
+ margin-bottom:10px;
+ /*border-bottom:1px solid #888a85;*/
+}
.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;}
+
+.questions-count{
+ font-size:32px;
+ font-family:sans-serif;
+ font-weight:600;
+ padding:0 0 5px 0px;
+ color:#a40000;
+ margin-top:3px;
+}
/*内容块*/
-.boxA {background:#777; padding:6px; margin-bottom:8px;}
+.boxA {background:#888a85; padding:6px; margin-bottom:8px;border 1px solid #babdb6;}
.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 {background:#F9F7ED; padding:6px; margin-bottom:8px;border:solid 1px #aaaaaa;}
+.boxB H3 {font-size:13px; font-weight:800; color:#000; margin:0; padding:0 0 0 15px; margin-bottom:4px; background:url(../images/dot-g.gif) no-repeat left center;}
+.boxB .body {border:1px solid #aaaaaa; padding:8px; background:#FFF; font-size:13px; line-height:160%;}
.boxB .more {padding:1px; text-align:right; font-weight:800;}
-.boxC {background:#F5F5F5; padding:6px; margin-bottom:8px;}
+.boxC {
+ background: #cacdc6;/*f9f7ed;*/
+ padding:10px;
+ margin-bottom:8px;
+ border-top:1px solid #eeeeec;
+ border-left:1px solid #eeeeec;
+ border-right:1px solid #a9aca5;
+ border-bottom:1px solid #babdb6;
+}
+.boxC p {
+ margin-bottom:8px;
+}
+.boxC p.nomargin {
+ margin:0px;
+}
+.boxC p.info-box-follow-up-links {
+ text-align:right;
+ margin:0;
+}
/*分页*/
.pager {margin-top:10px; margin-bottom:16px; float:left;}
.pagesize {margin-top:10px; margin-bottom:16px; float:right;}
@@ -197,7 +271,7 @@ blockquote
border:1px solid #fff;
background-color:#fff;
color:#777;
- padding:.3em;
+ padding:2px 4px 3px 4px;
font:bold 100% sans-serif;
}
@@ -248,37 +322,46 @@ blockquote
/*标签*/
.tag {font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
.tags {font-family:sans-serif; line-height:200%; display:block; margin-top:5px;}
-.tags a {font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
+.tags a {white-space: nowrap; font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
.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;}
+.marked-tags { margin-top: 0px;margin-bottom: 5px; }
+.deletable-tag { margin-right: 3px; white-space:nowrap; }
/*奖牌*/
-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;}
+a.medal { font-size:14px; line-height:250%; font-weight:800; color:#333; text-decoration:none; background:url(../images/medala.gif) no-repeat; border-left:1px solid #EEE; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:4px 12px 4px 6px;}
+a:hover.medal {color:#333; text-decoration:none; background:url(../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;}
+.tabBar{background-color:#FFF;border-bottom: 1px solid white;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;
+.tabsA a.on, .tabsA a:hover,.tabsB a.on, .tabsB a:hover {
+ background: #fff;
+ color:#a40000;
+ border-top:1px solid #babdb6;
+ border-left:1px solid #babdb6;
+ border-right:1px solid #888a85;
+ border-bottom:1px solid #888a85;
+ height: 24px;
line-height: 26px;
margin-top: 3px;
padding: 0px 11px 0px 11px;}
-.tabsA a {background: #eee;
- border: 1px solid #eee;
- color: #777;
+.tabsA a {
+ background: #f9f7eb;
+ border-top:1px solid #eeeeec;
+ border-left:1px solid #eeeeec;
+ border-right:1px solid #a9aca5;
+ border-bottom:1px solid #888a85;
+ color: #888a85;
display: block;
float: left;
- height: 22px;
- line-height: 28px;
+ height: 20px;
+ line-height: 22px;
margin: 5px 4px 0 0;
padding: 0 11px 0 11px;
text-decoration: none;
@@ -294,19 +377,28 @@ a:hover.medal {color:#333; text-decoration:none; background:url(/content/images
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;}
+/*.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;}
+.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(../images/dot-list.gif) no-repeat left center;}
+.headAnswers {float:left; padding:3px; font-size:18px; font-weight:800; background:url(../images/ico_answers.gif) left 2px no-repeat; padding-left:24px;}
+.headTags {float:left; padding:3px; font-size:18px; font-weight:800; background:url(../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(../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(../images/dot-list.gif) no-repeat left center;}
+.headLogin {float:left; padding:3px; font-size:15px; font-weight:800; background:url(../images/ico_login.gif) no-repeat; padding-left:24px;}
+.headNormal {
+ text-align:left;
+ padding:3px;
+ font-size:15px;
+ margin-bottom:12px;
+ font-weight:bold;
+ 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; }
+#feeds a {background:url(../images/feed-icon-small.png) no-repeat 0; padding-left:18px; font-weight:700; font-size:13px; }
/*问题*/
#question {margin-bottom:30px;}
@@ -354,9 +446,9 @@ a:hover.medal {color:#333; text-decoration:none; background:url(/content/images
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)}
+.question-img-upvote:hover{background:url(../images/vote-arrow-up-on.png)}
+.question-img-downvote:hover{background:url(../images/vote-arrow-down-on.png)}
+.question-img-favorite:hover{background:url(../images/vote-favorite-on.png)}
.favorite-number{padding:0px;font-size:100%; font-family:Arial;font-weight:bold;color:#777;}
.vote-notification
{
@@ -404,7 +496,7 @@ a:hover.medal {color:#333; text-decoration:none; background:url(/content/images
cursor:pointer;
}
-.action-link a:hover{
+.action-link: a hover{
background-color:#777;
text-decoration:none;
color:#fff;
@@ -424,8 +516,11 @@ div.comments {
}
div.post-comments{
+ clear:both;
+ background: url(../images/gray-up-arrow-h18px.png) no-repeat;
width:100%;
- margin-bottom:10px;
+ padding-left: 12px;
+ margin:3px 0 10px 0;
}
form.post-comments textarea {
@@ -441,6 +536,7 @@ form.post-comments input {
}
span.text-counter {
margin-right:20px;
+ font-size:11px;
}
span.form-error {
@@ -453,13 +549,7 @@ p.form-item {
}
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;
+ padding:0;
}
.post-comments a {
@@ -469,7 +559,9 @@ div.comments-container, a.comments-link {
a.comments-link, a.comments-link-accepted, a.comments-link-owner, a.comments-link-deleted {
color:black;
- padding:2px;
+ font-size:11px;
+ background: #eeeeee;
+ padding:3px;
cursor:pointer;
}
@@ -481,7 +573,7 @@ a.comments-link, a.comments-link-accepted, a.comments-link-owner, a.comments-lin
a.comment-user, a.comment-user:hover {
background-color:inherit;
- color:#0077CC;
+ color:blue;
padding:0;
}
@@ -493,6 +585,7 @@ a.comment-user:hover {
.answer{
padding-top:10px;
width: 100%;
+ border-bottom:1px solid #ccccce;
}
.answer-body{
min-height:80px;
@@ -555,7 +648,7 @@ a.comment-user:hover {
color: #E1E818;
}
-.answer-img-accept:hover{background:url(/content/images/vote-accepted-on.png)}
+.answer-img-accept:hover{background:url(../images/vote-accepted-on.png)}
.deleted{
background:#F4E7E7 none repeat scroll 0 0;
@@ -574,21 +667,44 @@ a.comment-user:hover {
.list-item LI{list-style-type:disc; font-size:13px; line-height:20px; margin-bottom:10px;}
/* openid styles */
.form-row{line-height:25px;}
+table.form-as-table {
+ margin-top:5px;
+}
+table.form-as-table ul {
+ list-style-type:none;
+ display: inline;
+}
+table.form-as-table li {
+ display: inline;
+}
+table.form-as-table td {
+ text-align:right;
+}
+table.form-as-table th {
+ text-align:left;
+ font-weight:normal;
+}
+/*.form-row li label {
+ display: inline
+}*/
.submit-row{line-height:30px;padding-top:10px;}
.errors{line-height:20px;color:red;}
-.error{color:red;}
+.error{
+ color:darkred;
+ margin:0;
+ font-size: 10px;
+}
.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-input{background:url(../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%;
+ background:url(../images/openid.gif) no-repeat 0% 50%;
padding:5px 5px 5px 15px;
cursor:pointer;
font-family:Trebuchet MS;
@@ -626,7 +742,7 @@ span.form-error {
margin-left:5px;
}
.title-desc{
- color:#999;
+ color:#666666;
font-size:90%;
}
@@ -704,6 +820,8 @@ span.form-error {
.revision .summary span{
background-color:yellow;
+ padding-left:3px;
+ padding-right:3px;
display:inline;
}
.revision h1{
@@ -715,6 +833,7 @@ span.form-error {
.revision-mark{
width:200px;
text-align:left;
+ display:inline-block;
font-size:90%;
overflow:hidden;
}
@@ -744,13 +863,13 @@ background-color: #97ff97;
}
/*用户资料页面*/
-.count {font-family:Arial;font-size:24px;font-weight:700;color:#777}
+.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;}
+.user-edit-link {background:url(../images/edit.png) no-repeat; padding-left:20px;}
.favorites-count-off {
color:#919191;
float:left;
@@ -814,7 +933,8 @@ width:950;margin-bottom:10px;
}
.narrow .summary {
- width:620px;
+ width:600px;
+ display:inline-block;
}
.narrow .summary h3 {
@@ -823,8 +943,8 @@ width:950;margin-bottom:10px;
}
.narrow .views {
- float:left;
height:42px;
+ float:left;
margin:0 7px 0 0;
/*padding:5px 0 5px 4px;*/
padding: 5px;
@@ -850,11 +970,22 @@ width:950;margin-bottom:10px;
.narrow .vote-count-post {
font-weight:800;
+ display:block;
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%;}
+.narrow .answer-count-post{
+ font-weight:800;
+ display:block;
+ margin:0;
+ font-size: 190%;
+}
+.narrow .views-count-post{
+ font-weight:800;
+ display:block;
+ margin:0;
+ font-size: 190%;
+}
div.started {
color:#999999;
float:right;
@@ -885,6 +1016,7 @@ div.started .reputation-score {
.narrow .tags{float:left;}
.answer-summary {
+ display:block;
clear:both;
padding:3px;
}
@@ -1008,7 +1140,7 @@ div.started .reputation-score {
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;}
+ul.bulleta li {background:url(../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;}
@@ -1027,92 +1159,291 @@ ul.bulleta li {background:url(/content/images/bullet_green.gif) no-repeat 0px 2p
.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;
+.message p.space-above {
+ margin-top:10px;
}
-.big {
- font-size:15px;
-}
-.bigger {
- font-size:14px;
-}
-.strong {
- font-weight:bold;
-}
-.orange
-{
- color:#d64000;
- font-weight:bold;
-}
-.grey {
- color:#808080;
-}
-.about div {
- padding:10px 5px 10px 5px;
- border-top:1px dashed #aaaaaa;
-}
-.about div.first {
- padding-top:0;
- border-top:none;
-}
-.about p {
- margin-bottom:10px;
-}
-.about a {color:#d64000;text-decoration:underline;}
-.about h3{
- line-height:30px;
- font-size:15px;
- font-weight:700;
- padding-top: 0px;
-}
-.highlight {
- background-color:#FFF8C6;
-}
+ .warning{color:red;}
+ .darkred{color:darkred;}
+ .submit{
+ cursor:pointer;
+ /*letter-spacing:1px;*/
+ background-color:#D4D0C8;
+ height:40px;
+ border:1px solid #777777;
+ /* width:100px; */
+ font-weight:bold;
+ padding-bottom:4px;
+ font-size:120%;}
+ .submit:hover{text-decoration:underline;}
+ .ask-body{padding-right:10px;}
+ .thousand{color:orange;}
+
+ .notify
+ {
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ z-index: 100;
+ padding: 0;
+ text-align: center;
+ font-weight: Bold;
+ color: #444;
+ background-color: #F4A83D;
+ }
+
+ .notify p {
+ margin-top:5px;
+ margin-bottom:5px;
+ font-size:16px;
+ }
+
+ #close-notify
+ {
+ position:absolute;
+ right:5px;
+ top:5px;
+ padding:0 3px 0 3px;
+ color: #735005;
+ text-decoration: none;
+ font-size:14px;
+ line-height:18px;
+ background-color: #FAD163;
+ border: 2px #735005 solid;
+ cursor:pointer;
+ }
+ #close-notify:hover {
+ text-decoration:none;
+ }
+
+ .big {
+ font-size:15px;
+ }
+ .bigger {
+ font-size:14px;
+ }
+ .strong {
+ font-weight:bold;
+ }
+ .orange
+ {
+ color:#d64000;
+ font-weight:bold;
+ }
+ .grey {
+ color:#808080;
+ }
+ .about div {
+ padding:10px 5px 10px 5px;
+ border-top:1px dashed #aaaaaa;
+ }
+ .about div.first {
+ padding-top:0;
+ border-top:none;
+ }
+ .about p {
+ margin-bottom:10px;
+ }
+ .about a {color:#d64000;text-decoration:underline;}
+ .about h3{
+ line-height:30px;
+ font-size:15px;
+ font-weight:700;
+ padding-top: 0px;
+ }
+ .highlight {
+ background-color:#FFF8C6;
+ }
+ .nomargin {
+ margin:0;
+ }
+ .margin-bottom {
+ margin-bottom: 10px;
+ }
+ .inline-block {
+ display:inline-block;
+ }
+ .action-status {
+ margin:0;
+ border:none;
+ text-align:center;
+ line-height:10px;
+ font-size:12px;
+ padding:0;
+ }
+ .action-status span {
+ padding:3px 5px 3px 5px;
+ background-color:#fff380;/* nice yellow */
+ font-weight:normal;
+ -moz-border-radius: 5px;
+ -khtml-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ }
+ .tight {
+ margin:0;
+ padding:0;
+ }
+
+ .list-table td {
+ vertical-align:top;
+ }
+
+ p.comment {
+ border-top: 1px dotted #ccccce;
+ margin:0;
+ font-size:11px;
+ color: #444444;
+ padding:5px 0 5px 0;
+ }
+
+ .delete-icon {
+ vertical-align:middle;
+ padding-left:3px;
+ }
+ /* these need to go */
+ table.form-as-table .errorlist {
+ display: block;
+ margin:0;
+ padding:0 0 0 5px;
+ text-align:left;
+ font-size:10px;
+ color:darkred;
+ }
+ table.form-as-table input {
+ display: inline;
+ margin-left: 4px;
+ }
+ table.form-as-table th {
+ vertical-align:bottom;
+ padding-bottom:4px;
+ }
+ .form-row-vertical {
+ margin-top: 8px;
+ display: block;
+ }
+ .form-row-vertical label {
+ margin-bottom:3px;
+ display:block;
+ }
+ /* above stuff needs to go */
+ .text-align-right {
+ text-align: center;
+ }
+ ul.form-horizontal-rows {
+ list-style:none;
+ margin:0;
+ }
+ ul.form-horizontal-rows li {
+ position:relative;
+ height:40px;
+ }
+ ul.form-horizontal-rows label {
+ display:inline-block;
+ }
+ ul.form-horizontal-rows ul.errorlist {
+ list-style:none;
+ color:darkred;
+ font-size:10px;
+ line-height:10px;
+ position:absolute;
+ top:2px;
+ left:180px;
+ text-align:left;
+ margin:0;
+ }
+ ul.form-horizontal-rows ul.errorlist li {
+ height:10px;
+ }
+ ul.form-horizontal-rows label {
+ position:absolute;
+ left:0px;
+ bottom:6px;
+ margin:0px;
+ line-height: 12px;
+ font-size: 12px;
+ }
+ ul.form-horizontal-rows li input {
+ position:absolute;
+ bottom:0px;
+ left:180px;
+ margin:0px;
+ }
+ #emailpw-form li input {
+ left:170px;
+ }
+ #emailpw-form ul.errorlist {
+ left:170px;
+ }
+ #changepw-form li input {
+ left:150px;
+ }
+ #changepw-form ul.errorlist {
+ left:150px;
+ }
+ .narrow .summary {
+ float: left;
+ }
+ .narrow .summary .question-title {
+ font-weight: bold;
+ font-size: 120%;
+ }
+ .user-profile-tool-links {
+ padding-bottom:10px;
+ font-weight: bold;
+ }
+ .post-controls {
+ float:left;
+ font-size:11px;
+ line-height:12px;
+ min-width:200px;
+ margin-bottom:5px;
+ }
+ #question-controls .tags {
+ margin:0 0 3px 0;
+ }
+ .post-update-info-container {
+ float: right;
+ min-width:190px;
+ }
+ .post-update-info {
+ display:inline-block;
+ float:right;
+ width:190px;
+ margin-bottom:5px;
+ }
+ .post-update-info p {
+ font-size:11px;
+ line-height:15px;
+ margin:0 0 4px 0;
+ padding:0;
+ }
+ .post-update-info img {
+ float: left;
+ width: 32px;
+ margin: 4px 8px 0 0;
+ }
+ .comments-container {
+ clear:both;
+ }
+ .admin {
+ background-color:#fff380;/* nice yellow */
+ border: 1px solid darkred;
+ padding: 0 5px 0 5px;
+ }
+ .admin p {
+ margin-bottom: 3px;
+ }
+ .admin #action_status {
+ text-align:center;
+ font-weight:bold;
+ }
+ #tagSelector {
+ padding-bottom: 2px;
+ }
+ #hideIgnoredTagsControl {
+ margin: 5px 0 0 0;
+ }
+ #hideIgnoredTagsCb {
+ margin: 0 2px 0 1px;
+ }
diff --git a/templates/edit_user_email_feeds_form.html b/templates/edit_user_email_feeds_form.html
new file mode 100644
index 00000000..65902e7e
--- /dev/null
+++ b/templates/edit_user_email_feeds_form.html
@@ -0,0 +1,4 @@
+{% load i18n %}
+<table class='form-as-table'>
+{{email_feeds_form.as_table}}
+</table>
diff --git a/templates/faq.html b/templates/faq.html
index aec37a56..236f4f76 100644
--- a/templates/faq.html
+++ b/templates/faq.html
@@ -72,10 +72,12 @@
<td style="text-align:right;padding-right:5px"><strong>500</strong></td>
<td>{% trans "retag questions" %}</td>
</tr>
+ {% if settings.WIKI_ON %}
<tr>
<td style="text-align:right;padding-right:5px"><strong>750</strong></td>
<td>{% trans "edit community wiki questions" %}</td>
</tr>
+ {% endif %}
<tr>
<td style="text-align:right;padding-right:5px"><strong>2000</strong></td>
<td>{% trans "edit any answer" %}</td>
@@ -99,7 +101,7 @@
<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" %}
+ {% blocktrans %}how to validate email info with {{send_email_key_url}} {{gravatar_faq_url}}{% endblocktrans %}
</div>
{% endifequal %}
<div>
@@ -121,7 +123,7 @@
</div>
<div>
<h3 class="subtitle">{% trans "Still have questions?" %}</h3>
- <p>{% trans "Please ask your question, help make our community better!" %}
+ <p>{% blocktrans %}Please ask your question at {{ask_question_url}}, help make our community better!{% endblocktrans %}
<!--
<a href="{% url tags %}faq" class="big">{{ settings.APP_TITLE }} {% trans "questions" %}</a>{% trans "." %}
-->
diff --git a/templates/feedback.html b/templates/feedback.html
new file mode 100644
index 00000000..38bb48ff
--- /dev/null
+++ b/templates/feedback.html
@@ -0,0 +1,55 @@
+{% extends "base_content.html" %}
+<!-- template about.html -->
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+{% block title %}{% spaceless %}{% trans "Feedback" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+{% endblock %}
+{% block content %}
+<div class="headNormal">
+{% trans "Give us your feedback!" %}
+</div>
+<div class="content">
+ <form method="post" action="{% url feedback %}" accept-charset="utf-8">
+ {% if user.is_authenticated %}
+ <p class="message">
+ {% blocktrans with user.username as user_name %}
+ <span class='big strong'>Dear {{user_name}}</span>, we look forward to hearing your feedback.
+ Please type and send us your message below.
+ {% endblocktrans %}
+ <p>
+ {% else %}
+ <p class="message">
+ {% blocktrans %}
+ <span class='big strong'>Dear visitor</span>, we look forward to hearing your feedback.
+ Please type and send us your message below.
+ {% endblocktrans %}
+ </p>
+ <div class="form-row"><label>{{form.name.label}}</label><br/>{{form.name}}</div>
+ <div class="form-row">
+ <label>{{form.email.label}}
+ {% if form.errors.email %}
+ <span class='red'>(please enter a valid email)</span>
+ {% endif %}
+ </label><br/>{{form.email}}
+ </div>
+ {% endif %}
+ <div class="form-row">
+ <label>{{form.message.label}}
+ {% if form.errors.message %}
+ <span class="red">{% trans "(this field is required)" %}</span>
+ </label>
+ {% endif %}
+ <br/>
+ {{form.message}}
+ </div>
+ {{form.next}}
+ <div class="submit-row">
+ <input type="submit" class="submit" value="{% trans "Send Feedback" %}"/>
+ <input type="submit" class="submit" name="cancel" value="{% trans "Cancel" %}"/>
+ </div>
+ </form>
+</div>
+{% endblock %}
+<!-- end template about.html -->
diff --git a/templates/feedback_email.txt b/templates/feedback_email.txt
new file mode 100644
index 00000000..df768180
--- /dev/null
+++ b/templates/feedback_email.txt
@@ -0,0 +1,19 @@
+{% load i18n %}
+{% spaceless %}
+{% blocktrans with settings.APP_TITLE|safe as site_title %}
+Hello, this is a {{site_title}} forum feedback message
+{% endblocktrans %}
+{% endspaceless %}
+
+{% spaceless %}
+{% trans "Sender is" %}
+{% if user.is_authenticated %}
+ {{user.username|safe}} {% trans "email" %}:{{user.email|safe}}
+{% else %}
+ {% if name %}{{name|safe}}{% else %}{% trans "anonymous" %}{% endif %}
+ {% if email %}{% trans "email" %}:{{email|safe}}{% endif %}
+{% endif %}
+ ip:{{request.META.REMOTE_ADDR}}
+{% endspaceless %}
+
+{% trans "Message body:" %} {{message|safe}}
diff --git a/templates/footer.html b/templates/footer.html
index 34064fd5..9d19b41e 100644
--- a/templates/footer.html
+++ b/templates/footer.html
@@ -3,33 +3,47 @@
{% load i18n %}
<!-- 页面底部开始: -->
<div id="ground">
- <div class="footerLinks" >
- <a href="{% url about %}">{% trans "about" %}</a><span class="link-separator"> |</span>
- <a href="{% url faq %}">{% trans "faq" %}</a><span class="link-separator"> |</span>
- <a href="{{ blog_url }}">{% trans "blog" %}</a><span class="link-separator"> |</span>
- <a href="{{ webmaster_email }}">{% trans "contact us" %}</a><span class="link-separator"> |</span>
- <a href="{% url privacy %}">{% trans "privacy policy" %}</a><span class="link-separator"> |</span>
- <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>
+ <div class="footerLinks" >
+ <a href="{% url about %}">{% trans "about" %}</a><span class="link-separator"> |</span>
+ <a href="{% url faq %}">{% trans "faq" %}</a><span class="link-separator"> |</span>
+ <!--<a href="{{ blog_url }}">{% trans "blog" %}</a><span class="link-separator"> |</span>-->
+ <!--<a href="{{ webmaster_email }}">{% trans "contact us" %}</a><span class="link-separator"> |</span>-->
+ <a href="{% url privacy %}">{% trans "privacy policy" %}</a><span class="link-separator"> |</span>
+ {% spaceless %}
+ <a href=
+ {% if settings.FEEDBACK_SITE_URL %}
+ "{{settings.FEEDBACK_SITE_URL}}"
+ target="_blank">
+ {% else %}
+ "{% url feedback %}?next={{request.path}}">
+ {% endif %}
+ {% trans "give feedback" %}
+ </a>
+ {% endspaceless %}
+ </div>
+ <p>
+ <a href="http://github.com/cnprog/CNPROG/network" target="_blank">
+ powered by cnprog platform
+ <!--<img src="{% href "/content/images/djangomade124x25_grey.gif" %}" border="0" alt="Made with Django." title="Made with Django." >-->
+ </a>
+ </p>
+ </div>
+ <div id="licenseLogo">
+ <a href="http://creativecommons.org/licenses/by/3.0/">
+ <img src="{% href "/content/images/cc-wiki.png" %}" title="Creative Commons: Attribution - Share Alike" alt="cc-wiki" width="50" height="68" />
+ </a>
+ </div>
</div>
<!-- 页面底部结束: -->
- {% if settings.GOOGLE_ANALYTICS_KEY %}
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
- var pageTracker = _gat._getTracker("{{ settings.GOOGLE_ANALYTICS_KEY }}");
+ var pageTracker = _gat._getTracker('{{ settings.GOOGLE_ANALYTICS_KEY }}');
pageTracker._trackPageview();
} catch(err) {}
</script>
- {% endif %}
-<!-- end footer.html -->
+<!-- end template footer.html -->
diff --git a/templates/header.html b/templates/header.html
index c9b01a20..ede6cce5 100644
--- a/templates/header.html
+++ b/templates/header.html
@@ -4,23 +4,21 @@
<div id="roof">
<div id="navBar">
<div id="top">
- <!--<div id="header">-->
- {% if request.user.is_authenticated %}
- <a href="{% url users %}{{ request.user.id }}/{{ request.user.username }}/">{{ request.user.username }}</a> {% get_score_badge request.user %}
- <a href="{% url logout %}">{% trans "logout" %}</a>
- {% else %}
- <a href="{% url user_signin %}">{% trans "login" %}</a>
- {% endif %}
- <a href="{% url about %}">{% trans "about" %}</a>
- <a href="{% url faq %}">{% trans "faq" %}</a>
- <!--</div>-->
+ {% if request.user.is_authenticated %}
+ <a href="{% url users %}{{ request.user.id }}/{{ request.user.username }}/">{{ request.user.username }}</a> {% get_score_badge request.user %}
+ <a href="{% url logout %}">{% trans "logout" %}</a>
+ {% else %}
+ <a href="{% url user_signin %}">{% trans "login" %}</a>
+ {% endif %}
+ <a href="{% url about %}">{% trans "about" %}</a>
+ <a href="{% url faq %}">{% trans "faq" %}</a>
</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" %}" alt="{{settings.APP_TITLE}} logo"/>
+ <a href="{% url index %}">
+ <img src="{% href "/content/images/logo.png" %}" title="{% trans "back to home page" %}" alt="{{settings.APP_TITLE}} logo"/>
</a>
</div>
</td>
@@ -60,9 +58,9 @@
</div>
<div class="options">
<input id="type-question" type="radio" value="question" name="t"
- checked="checked" />{% trans "questions" %}
- <input id="type-tag" type="radio" value="tag" name="t" />{% trans "tags" %}
- <input id="type-user" type="radio" value="user" name="t" />{% trans "users" %}
+ checked="checked" /><label for="type-question">{% trans "questions" %}</label>
+ <input id="type-tag" type="radio" value="tag" name="t" /><label for="type-tag">{% trans "tags" %}</label>
+ <input id="type-user" type="radio" value="user" name="t" /><label for="type-user">{% trans "users" %}</label>
</div>
</form>
</td>
diff --git a/templates/index.html b/templates/index.html
index bc83b637..b920db1b 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -4,18 +4,21 @@
{% load extra_tags %}
{% load humanize %}
{% load extra_filters %}
+{% load smart_if %}
{% block title %}{% spaceless %}{% trans "Home" %}{% endspaceless %}{% 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(){
- var tab_id = "{{ tab_id }}";
- $("#"+tab_id).attr('className',"on");
- $("#nav_questions").attr('className',"on");
- });
-
- </script>
+ var tags = {{ tags_autocomplete|safe }};
+ $().ready(function(){
+ var tab_id = "{{ tab_id }}";
+ $("#"+tab_id).attr('className',"on");
+ $("#nav_questions").attr('className',"on");
+ });
+ </script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.tag_selector.js" %}'></script>
{% endblock %}
{% block content %}
<div class="tabBar">
@@ -33,30 +36,31 @@
{% for question in questions %}
<div class="qstA">
<h2>
- <a href="{{ question.get_absolute_url }}" title="{{ question.summary|collapse }}...">
- {{ question.get_question_title }}
- </a>
+ <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
</h2>
<div class="stat">
<table>
- <tr>
+ <tr>
<td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
<td><span class="num">{{ question.score|intcomma }}</span> </td>
<td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
- </tr>
+ </tr>
<tr>
<td><span class="unit">{% trans "answers" %}</span></td>
<td><span class="unit">{% trans "votes" %}</span></td>
<td><span class="unit">{% trans "views" %}</span></td>
- </tr>
+ </tr>
</table>
</div>
+
<div class="summary">
- {{ question.summary}}...
+ {{ question.summary }}...
</div>
- {% if question.wiki %}
+
+ {% ifequal tab_id 'active'%}
+ {% if question.wiki and settings.WIKI_ON %}
<span class="from wiki">{% trans "community wiki" %}</span>
- <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
{% else %}
<div class="from">
{% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
@@ -65,6 +69,38 @@
<span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
</div>
{% endif %}
+ {% else %}
+ {% if question.wiki and settings.WIKI_ON %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.author 24 %}{% endcomment %}
+ {% if question.last_activity_at != question.added_at %}
+ {% if question.author.id != question.last_activity_by.id %}
+ {% trans "Posted:" %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ / {% trans "Updated:" %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ {% else %}
+ {% trans "Updated:" %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ {% endif %}
+ {% else %}
+ {% trans "Posted:" %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% endif %}
+ </div>
+ {% endif %}
+ {% endifequal %}
+
<div class="tags">
{% for tag in question.tagname_list %}
<a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
@@ -85,8 +121,10 @@
<div class="more"><a href="{% url faq %}">{% trans "faq" %} »</a></div>
</div>
</div>
+{% else %}
+{% include "tag_selector.html" %}
{% endif %}
-<div class="boxB">
+<div class="boxC">
<h3>{% trans "Recent tags" %}</h3>
<div class="body">
<div class="tags">
@@ -99,7 +137,7 @@
</div>
</div>
{% if awards %}
-<div class="boxB">
+<div class="boxC">
<h3>{% trans "Recent awards" %}</h3>
<div class="body">
<ul class="badge-list">
@@ -116,12 +154,12 @@
</div>
{% endif %}
<div id="feeds">
-<a href="/feeds/rss" title="{% trans "subscribe to last 30 questions by RSS" %}">{% trans "subscribe to the questions feed" %}</a>
+<a href="{% href "/feeds/rss" %}" title="{% trans "subscribe to last 30 questions by RSS" %}">{% trans "subscribe to the questions feed" %}</a>
</div>
{% endblock %}
{% block tail %}
<div style="padding:5px 0 5px 5px;">
-<span class="evenMore">{% trans "Still looking for more? See" %} <a href="{% url questions %}">{% trans "complete list of questions" %}</a> {% trans "or" %} <a href="/tags/">{% trans "popular tags" %}</a>{% trans "." %} {% trans "Please help us answer" %} <a href="{% url questions %}unanswered">{% trans "list of unanswered questions" %}</a>{% trans "." %}</span>
+<span class="evenMore">{% trans "Still looking for more? See" %} <a href="{% url questions %}">{% trans "complete list of questions" %}</a> {% trans "or" %} <a href="{% url tags %}">{% trans "popular tags" %}</a>{% trans "." %} {% trans "Please help us answer" %} <a href="{% url 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 2cd935a0..650ba044 100644
--- a/templates/logout.html
+++ b/templates/logout.html
@@ -8,7 +8,6 @@
<script type="text/javascript">
$().ready(function(){
$('#btLogout').bind('click', function(){ window.location.href='{% url user_signout %}?next={{ next }}'; });
-
});
</script>
{% endblock %}
diff --git a/templates/post_contributor_info.html b/templates/post_contributor_info.html
new file mode 100644
index 00000000..9997be5f
--- /dev/null
+++ b/templates/post_contributor_info.html
@@ -0,0 +1,55 @@
+{% load i18n %}
+{% load smart_if %}
+{% load extra_tags %}
+<div class='post-update-info'>
+{% ifequal contributor_type "original_author" %}
+ {% if wiki %}
+ <p>{% trans "community wiki" %}</p>
+ <p>
+ {% blocktrans count post.revisions.all|length as rev_count %}
+ one revision
+ {% plural %}
+ {{rev_count}} revisions
+ {% endblocktrans %}
+ </p>
+ <p>{{post.author.get_profile_link}}</p>
+ {% else %}
+ <p style="line-height:12px;">
+ {% ifequal post_type "question" %}
+ {% trans "asked" %}
+ {% else %}
+ {% ifequal post_type "answer" %}
+ {% trans "answered" %}
+ {% else %}
+ {% trans "posted" %}
+ {% endifequal %}
+ {% endifequal %}
+ {% ifequal post_type "revision" %}
+ <strong>{% diff_date post.revised_at %}</strong>
+ {% else %}
+ <strong>{% diff_date post.added_at %}</strong>
+ {% endifequal %}
+ </p>
+ {% gravatar post.author 32 %}
+ <p>{{post.author.get_profile_link}}<br/>
+ {% get_score_badge post.author %}</p>
+ {% endif %}
+{% else %}
+ {% if post.last_edited_at %}
+ <p style="line-height:12px;">
+ {% ifequal post_type 'question' %}
+ <a href="{% url question_revisions post.id %}">
+ {% else %}
+ <a href="{% url answer_revisions post.id %}">
+ {% endifequal %}
+ {% trans "updated" %} <strong>{% diff_date post.last_edited_at %}</strong>
+ </a>
+ </p>
+ {% if post.author != post.last_edited_by or wiki %}
+ {% gravatar post.last_edited_by 32 %}
+ <p style="float:left">{{post.last_edited_by.get_profile_link}}<br/>
+ {% get_score_badge post.last_edited_by %}</p>
+ {% endif %}
+ {% endif %}
+{% endifequal %}
+</div>
diff --git a/templates/question.html b/templates/question.html
index 7d7c9c4c..9652e3f6 100644
--- a/templates/question.html
+++ b/templates/question.html
@@ -2,21 +2,23 @@
<!-- question.html -->
{% load extra_tags %}
{% load extra_filters %}
+{% load smart_if %}
{% load humanize %}
{% load i18n %}
{% block title %}{% spaceless %}{{ question.get_question_title }}{% endspaceless %}{% endblock %}
{% block forejs %}
<meta name="description" content="{{question.summary}}" />
<meta name="keywords" content="{{question.tagname_meta_generator}}" />
- <link rel="canonical" href="{{settings.APP_URL}}{{question.get_absolute_url}}"/>
+ <link rel="canonical" href="{{settings.APP_URL}}{{question.get_absolute_url}}" />
{% if not question.closed %}
- <script type='text/javascript' src='/content/js/com.cnprog.editor.js'></script>
- <script type='text/javascript' src='/content/js/wmd/showdown.js'></script>
- <script type='text/javascript' src='/content/js/wmd/wmd.js'></script>
- <link rel="stylesheet" type="text/css" href="/content/js/wmd/wmd.css" />
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/showdown.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/wmd.js" %}'></script>
+ <link rel="stylesheet" type="text/css" href="{% href "/content/js/wmd/wmd.css" %}" />
{% endif %}
- <script type='text/javascript' src='/content/js/com.cnprog.post.js'></script>
- <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.post.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
+
<script type="text/javascript">
// define reputation needs for comments
var repNeededForComments = 50;
@@ -61,14 +63,17 @@
</div>
<div id="main-body" class="">
<div id="askform">
- <form id="fmanswer" action="{% url answer question.id %}" method="post">
<table style="width:100%;" id="question-table" {% if question.deleted %}class="deleted"{%endif%}>
<tr>
<td style="width:30px;vertical-align:top">
<div class="vote-buttons">
{% if question_vote %}
<img id="question-img-upvote-{{ question.id }}" class="question-img-upvote"
- src="/content/images/vote-arrow-up{% if question_vote.is_upvote %}-on{% endif %}.png"
+ {% if question_vote.is_upvote %}
+ src="{% href "/content/images/vote-arrow-up-on.png" %}"
+ {% else %}
+ src="{% href "/content/images/vote-arrow-up.png" %}"
+ {% endif %}
alt="{% trans "i like this post (click again to cancel)" %}"
title="{% trans "i like this post (click again to cancel)" %}" />
<div id="question-vote-number-{{ question.id }}" class="vote-number"
@@ -76,34 +81,38 @@
{{ question.score }}
</div>
<img id="question-img-downvote-{{ question.id }}" class="question-img-downvote"
- src="/content/images/vote-arrow-down{% if question_vote.is_downvote %}-on{% endif %}.png"
+ {% if question_vote.is_downvote %}
+ src="{% href "/content/images/vote-arrow-down-on.png" %}"
+ {% else %}
+ src="{% href "/content/images/vote-arrow-down.png" %}"
+ {% endif %}
alt="{% trans "i dont like this post (click again to cancel)" %}"
title="{% trans "i dont like this post (click again to cancel)" %}" />
{% else %}
<img id="question-img-upvote-{{ question.id }}" class="question-img-upvote"
alt="{% trans "i like this post (click again to cancel)" %}"
- src="/content/images/vote-arrow-up.png"
+ src="{% href "/content/images/vote-arrow-up.png" %}"
title="{% trans "i like this post (click again to cancel)" %}" />
<div id="question-vote-number-{{ question.id }}" class="vote-number"
title="{% trans "current number of votes" %}">
{{ question.score }}
</div>
<img id="question-img-downvote-{{ question.id }}" class="question-img-downvote"
- src="/content/images/vote-arrow-down.png"
+ src="{% href "/content/images/vote-arrow-down.png" %}"
alt="{% trans "i dont like this post (click again to cancel)" %}"
title="{% trans "i dont like this post (click again to cancel)" %}" />
{% endif %}
{% if favorited %}
- <img class="question-img-favorite" src="/content/images/vote-favorite-on.png"
+ <img class="question-img-favorite" src="{% href "/content/images/vote-favorite-on.png" %}"
alt="{% trans "mark this question as favorite (click again to cancel)" %}"
title="{% trans "mark this question as favorite (click again to cancel)" %}" />
<div id="favorite-number" class="favorite-number my-favorite-number">
{{ question.favourite_count }}
</div>
{% else %}
- <img class="question-img-favorite" src="/content/images/vote-favorite-off.png"
+ <img class="question-img-favorite" src="{% href "/content/images/vote-favorite-off.png" %}"
alt="{% trans "remove favorite mark from this question (click again to restore mark)" %}"
title="{% trans "remove favorite mark from this question (click again to restore mark)" %}" />
<div id="favorite-number" class="favorite-number">
@@ -119,135 +128,86 @@
<div class="question-body">
{{ question.html|safe }}
</div>
- <div id="question-tags" class="tags" >
- {% for tag in question.tagname_list %}
- <a href="{% url forum.views.tag tag|urlencode %}" class="post-tag"
- title="{% blocktrans with tag as tagname %}see questions tagged '{{ tagname }}'{% endblocktrans %}" rel="tag">{{ tag }}</a>
- {% endfor %}
+ <div id="question-controls" class="post-controls">
+ <div id="question-tags" class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" class="post-tag"
+ title="{% blocktrans with tag as tagname %}see questions tagged '{{ tagname }}'{% endblocktrans %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ {% joinitems using '<span class="action-link-separator">|</span>' %}
+ {% if request.user|can_edit_post:question %}
+ <span class="action-link"><a href="{% url edit_question question.id %}">{% trans 'edit' %}</a></span>
+ {% endif %}
+ {% separator %}
+ {% if question.closed %}
+ {% if request.user|can_reopen_question:question %}
+ <span class="action-link"><a href="{% url reopen question.id %}">{% trans "reopen" %}</a></span>
+ {% endif %}
+ {% else %}
+ {% if request.user|can_close_question:question %}
+ <span class="action-link"><a href="{% url close question.id %}">{% trans "close" %}</a></span>
+ {% endif %}
+ {% endif %}
+ {% separator %}
+ {% if request.user|can_flag_offensive %}
+ <span id="question-offensive-flag-{{ question.id }}" class="offensive-flag"
+ title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
+ <a>{% trans "flag offensive" %}</a>
+ {% if request.user|can_view_offensive_flags and question.offensive_flag_count %}
+ <span class="darkred">({{ question.offensive_flag_count }})</span>
+ {% endif %}
+ </span>
+ {% endif %}
+ {% separator %}
+ {% if request.user|can_delete_post:question %}
+ <span class="action-link"><a id="question-delete-link-{{question.id}}">{% trans "delete" %}</a></span>
+ {% endif %}
+ {% endjoinitems %}
</div>
- {% trans "Category: " %} <a href="{% url forum.views.category question.category|urlencode %}">{{question.category}}</a>
- <div id="question-controls" style="clear:both;">
- <table width="100%">
- <tr>
- <td width="210px" style="vertical-align:top">
-
- {% if request.user|can_edit_post:question %}
- <span class="action-link"><a href="{% url edit_question question.id %}">{% trans 'edit' %}</a></span>
- <span class="action-link-separator">|</span>
- {% endif %}
- {% if request.user|can_delete_post:question %}
- <span class="action-link"><a id="question-delete-link-{{question.id}}">{% trans "delete" %}</a></span>
- <span class="action-link-separator">|</span>
- {% endif %}
- {% if question.closed %}
- {% if request.user|can_reopen_question:question %}
- <span class="action-link"><a href="{% url reopen question.id %}">{% trans "reopen" %}</a></span>
- <span class="action-link-separator">|</span>
- {% endif %}
- {% else %}
- {% if request.user|can_close_question:question %}
- <span class="action-link"><a href="{% url close question.id %}">{% trans "close" %}</a></span>
- <span class="action-link-separator">|</span>
- {% endif %}
- {% endif %}
-
- <span id="question-offensive-flag-{{ question.id }}" class="offensive-flag"
- title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
- <a>{% trans "flag offensive" %}</a>
- <span class="darkred">{% if request.user|can_view_offensive_flags %}
- {% if question.offensive_flag_count %}({{ question.offensive_flag_count }}){% endif %}{% endif %}</span>
- </span>
-
- </td>
- <td width="210px" style="vertical-align:top">
- {% if question.last_edited_by %}
- <div class="question-edit" >
- <table width="200px" >
- <tr>
- <td colspan="2">
- {% trans "updated" %} <a href="{% url question_revisions question.id %}"><strong title="{{question.last_edited_at }}">{% diff_date question.last_edited_at %}</strong></a>
- </td>
-
- </tr>
- {% if question.wiki %}
- <tr>
- <td style="width:40px;vertical-align:bottom">
- {% gravatar question.last_edited_by 32 %}
- </td>
- <td style="width:160px; vertical-align:top">
- <a href="{% url users %}{{ question.last_edited_by.id }}/{{ question.last_edited_by.username }}">{{ question.last_edited_by.username }}</a>
- </td>
- </tr>
- {% else %}
- {% ifequal question.last_edited_by question.author %}
- <tr>
- <td> </td>
- <td> </td>
- </tr>
- {% else %}
- <tr>
- <td style="width:40px;vertical-align:bottom">
- {% gravatar question.last_edited_by 32 %}
- </td>
- <td style="width:160px; vertical-align:top">
- <div><a href="{% url users %}{{ question.last_edited_by.id }}/{{ question.last_edited_by.username }}">{{ question.last_edited_by.username }}</a></div>
-
- <div>
- {% get_score_badge question.last_edited_by %}
- </div>
-
- </td>
- </tr>
- {% endifequal %}
- {% endif %}
- </table>
- </div>
- {% endif %}
-
- </td>
- <td style="vertical-align:top">
- {% if question.wiki %}
- <span class="wiki-category">{% trans "community wiki" %}</span>
- <div style="margin-bottom:10px"></div>
- {% else %}
- <div class="question-mark">
- <table width="200px">
- <tr>
- <td colspan="2">
- {% trans "asked" %} <strong title="{{ question.added_at }}">{% diff_date question.added_at %}</strong>
- </td>
-
- </tr>
-
- <tr>
- <td style="width:40px; vertical-align:bottom">
- {% gravatar question.author 32 %}
- </td>
- <td align="left" style="width:160px;vertical-align:top">
- <div><a href="{% url users %}{{ question.author.id }}/{{ question.author }}">{{ question.author }}</a></div>
- <div>
- {% get_score_badge question.author %}
- </div>
- </td>
- </tr>
-
- </table>
- </div>
- {% endif %}
-
- </td>
- </tr>
- </table>
-
+ <div class="post-update-info-container">
+ {% post_contributor_info question "original_author" %}
+ {% post_contributor_info question "last_updater" %}
+ </div>
+ <div class="comments-container" id="comments-container-question-{{question.id}}">
+ {% for comment in question.get_comments|slice:":5" %}
+ <p class="comment" id="comment-{{comment.id}}">
+ {{comment.comment}}
+ - <a class="comment-user" href="{{comment.user.get_profile_url}}">{{comment.user}}</a>
+ {% spaceless %}
+ <span class="comment-age">({% diff_date comment.added_at %})</span>
+ {% if request.user|can_delete_comment:comment %}
+ <img class="delete-icon"
+ src="{% href "/content/images/close-small.png" %}"
+ title="{% trans "delete this comment" %}"/>
+ {% endif %}
+ {% endspaceless %}
+ </p>
+ {% endfor %}
</div>
<div class="post-comments" style="margin-bottom:20px">
- <input id="can-post-comments-question-{{question.id}}" type="hidden" value="{{ request.user|can_add_comments }}"/>
- <a id="comments-link-question-{{question.id}}" class="comments-link">
- {% if question.comment_count %}{% trans "comments" %} <strong>({{question.comment_count}})</strong>
- {% else %}{% trans "add comment" %}
- {% endif %}</a>
- <div id="comments-question-{{question.id}}" class="comments-container">
- <div class="comments"/></div>
+ <input id="can-post-comments-question-{{question.id}}" type="hidden" value="{{ request.user|can_add_comments:question }}"/>
+ {% if request.user|can_add_comments:question or question.comment_count > 5 %}
+ <a id="comments-link-question-{{question.id}}" class="comments-link">
+ {% if request.user|can_add_comments:question %}
+ {% trans "add comment" %}
+ {% endif %}
+ {% if question.comment_count > 5 %}
+ {% if request.user|can_add_comments:question %}/
+ {% blocktrans count question.get_comments|slice:"5:"|length as counter %}
+ see <strong>one</strong> more
+ {% plural %}
+ see <strong>{{counter}}</strong> more
+ {% endblocktrans %}
+ {% else %}
+ {% blocktrans count question.get_comments|slice:"5:"|length as counter %}
+ see <strong>one</strong> more comment
+ {% plural %}
+ see <strong>{{counter}}</strong> more comments
+ {% endblocktrans %}
+ {% endif %}
+ {% endif %}</a>
+ {% endif %}
</div>
</div>
@@ -256,20 +216,29 @@
</table>
{% if question.closed %}
<div class="question-status" style="margin-bottom:15px">
- <h3>{% trans "The question has been closed for the following reason" %} "{{ question.get_close_reason_display }}" {% trans "by"%}
+ <h3>{% blocktrans with question.get_close_reason_display as close_reason %}The question has been closed for the following reason "{{ close_reason }}" by{% endblocktrans %}
<a href="{{ question.closed_by.get_profile_url }}">{{ question.closed_by.username }}</a>
- {% blocktrans %}close date {% endblocktrans %} {{question.closed_at|date:"d-m-Y H:i"}}</h3>
+ {% blocktrans with question.closed_at as closed_at %}close date {{closed_at}}{% endblocktrans %}</h3>
</div>
{% endif %}
-
- {% ifnotequal answers.length 0 %}
+ {% if answers %}
+ <hr/>
<div class="tabBar">
<a name="sort-top"></a>
- <div class="headQuestions">{{ answers|length }}{% trans "Answers" %}:</div>
+ <div class="headQuestions">
+ {% blocktrans count answers|length as counter %}
+ One Answer:
+ {% plural %}
+ {{counter}} Answers:
+ {% endblocktrans %}
+ </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>
- <a id="votes" href="?sort=votes#sort-top" title="{% trans "most voted answers will be shown first" %}">{% trans "popular answers" %}</a>
+ <a id="oldest" href="{% url question question.id %}?sort=oldest#sort-top"
+ title="{% trans "oldest answers will be shown first" %}">{% trans "oldest answers" %}</a>
+ <a id="latest" href="{% url question question.id %}?sort=latest#sort-top"
+ title="{% trans "newest answers will be shown first" %}">{% trans "newest answers" %}</a>
+ <a id="votes" href="{% url question question.id %}?sort=votes#sort-top"
+ title="{% trans "most voted answers will be shown first" %}">{% trans "popular answers" %}</a>
</div>
</div>
{% cnprog_paginator context %}
@@ -282,26 +251,26 @@
<td style="width:30px;vertical-align:top">
<div class="vote-buttons">
<img id="answer-img-upvote-{{ answer.id }}" class="answer-img-upvote"
- src="/content/images/vote-arrow-up{% get_user_vote_image user_answer_votes answer.id 1 %}.png"
+ src="{% blockresource %}/content/images/vote-arrow-up{% get_user_vote_image user_answer_votes answer.id 1 %}.png{% endblockresource %}"
alt="{% trans "i like this answer (click again to cancel)" %}"
title="{% trans "i like this answer (click again to cancel)" %}"/>
<div id="answer-vote-number-{{ answer.id }}" class="vote-number" title="{% trans "current number of votes" %}">
{{ answer.score }}
</div>
<img id="answer-img-downvote-{{ answer.id }}" class="answer-img-downvote"
- src="/content/images/vote-arrow-down{% get_user_vote_image user_answer_votes answer.id -1 %}.png"
+ src="{% blockresource %}/content/images/vote-arrow-down{% get_user_vote_image user_answer_votes answer.id -1 %}.png{% endblockresource %}"
alt="{% trans "i dont like this answer (click again to cancel)" %}"
title="{% trans "i dont like this answer (click again to cancel)" %}" />
{% ifequal request.user question.author %}
<img id="answer-img-accept-{{ answer.id }}" class="answer-img-accept"
- src="/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png"
+ src="{% blockresource %}/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png{% endblockresource %}"
alt="{% trans "mark this answer as favorite (click again to undo)" %}"
title="{% trans "mark this answer as favorite (click again to undo)" %}" />
{% else %}
{% if answer.accepted %}
<img id="answer-img-accept-{{ answer.id }}" class="answer-img-accept"
- src="/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png"
+ src="{% blockresource %}/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png{% endblockresource %}"
alt="{% trans "the author of the question has selected this answer as correct" %}"
title="{% trans "the author of the question has selected this answer as correct" %}" />
{% endif %}
@@ -313,116 +282,83 @@
<div class="answer-body">
{{ answer.html|safe }}
</div>
- <div class="answer-controls" style="clear:both;">
- <table width="100%">
- <tr>
- <td width="400px" style="vertical-align:top">
- {% if request.user|can_edit_post:answer %}
- <span class="action-link"><a href="{% url edit_answer answer.id %}">{% trans "edit" %}</a></span>
- <span class="action-link-separator">|</span>
- {% endif %}
- {% if request.user|can_delete_post:answer %}
- <span class="action-link">
- <a id="answer-delete-link-{{answer.id}}">
- {% if answer.deleted %}
- {% trans "undelete" %}
- {% endif %}
- {% if not answer.deleted %}
- {% trans "delete" %}
- {% endif %}
- </a>
- </span>
- <span class="action-link-separator">|</span>
- {% endif %}
- <span class="linksopt">
- <a href="#{{ answer.id }}" title="{% trans "answer permanent link" %}">
- {% trans "permanent link" %}
- </a>
- </span>
- <span class="action-link-separator">|</span>
- <span id="answer-offensive-flag-{{ answer.id }}" class="offensive-flag"
- title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
- <a>{% trans "flag offensive" %}</a>
- <span class="darkred">{% if request.user|can_view_offensive_flags %}{% if answer.offensive_flag_count %}({{ answer.offensive_flag_count }}){% endif %}{% endif %}</span></span>
- </td>
- <td width="110px" style="vertical-align:top">
- {% if answer.last_edited_by %}
- <div class="question-edit" >
- <table width="200px" >
- <tr>
- <td colspan="2">
- {% trans "updated" %}<a href="{% url answer_revisions answer.id %}"><strong title="{{answer.last_edited_at }}">{% diff_date answer.last_edited_at %}</strong></a>
- </td>
- </tr>
- {% if answer.wiki %}
- <tr>
- <td width="40px" style="vertical-align:bottom">
- {% gravatar answer.last_edited_by 32 %}
- </td>
- <td style="width:160px; vertical-align:top">
- <div><a href="{% url users %}{{ answer.last_edited_by.id }}/{{ answer.last_edited_by.username }}">{{ answer.last_edited_by.username }}</a></div>
-
- </td>
- </tr>
- {% else %}
- {% ifequal answer.last_edited_by answer.author %}
- <tr>
- <td> </td>
- <td> </td>
- </tr>
- {% else %}
- <tr>
- <td width="40px" style="vertical-align:bottom">
- {% gravatar answer.last_edited_by 32 %}
- </td>
- <td style="width:160px; vertical-align:top">
- <div><a href="{% url users %}{{ answer.last_edited_by.id }}/{{ answer.last_edited_by.username }}">{{ answer.last_edited_by.username }}</a></div>
- <div>
- {% get_score_badge answer.last_edited_by %}
- </div>
- </td>
- </tr>
- {% endifequal %}
- {% endif %}
- </table>
- </div>
+ <div class="answer-controls post-controls">
+ {% joinitems using '<span class="action-link-separator">|</span>' %}
+ <span class="linksopt">
+ <a href="#{{ answer.id }}" title="{% trans "answer permanent link" %}">
+ {% trans "permanent link" %}
+ </a>
+ </span>
+ {% separator %}
+ {% if request.user|can_edit_post:answer %}
+ <span class="action-link"><a href="{% url edit_answer answer.id %}">{% trans 'edit' %}</a></span>
+ {% endif %}
+ {% separator %}
+
+ {% if request.user|can_flag_offensive %}
+ <span id="answer-offensive-flag-{{ answer.id }}" class="offensive-flag"
+ title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
+ <a>{% trans "flag offensive" %}</a>
+ {% if request.user|can_view_offensive_flags and answer.offensive_flag_count %}
+ <span class="darkred">({{ answer.offensive_flag_count }})</span>
{% endif %}
-
- </td>
- <td style="vertical-align:top">
- {% if answer.wiki %}
- <span class="wiki-category">{% trans "community wiki" %}</span>
- <div style="margin-bottom:10px"></div>
+ </span>
+ {% endif %}
+ {% separator %}
+ {% if request.user|can_delete_post:answer %}
+ {% spaceless %}
+ <span class="action-link">
+ <a id="answer-delete-link-{{answer.id}}">
+ {% if answer.deleted %}{% trans "undelete" %}{% else %}{% trans "delete" %}{% endif %}</a>
+ </span>
+ {% endspaceless %}
+ {% endif %}
+ {% endjoinitems %}
+ </div>
+ <div class="post-update-info-container">
+ {% post_contributor_info answer "original_author" %}
+ {% post_contributor_info answer "last_updater" %}
+ </div>
+ <div class="comments-container" id="comments-container-answer-{{answer.id}}">
+ {% for comment in answer.get_comments|slice:":5" %}
+ <p id="comment-{{comment.id}}" class="comment">
+ {{comment.comment}}
+ - <a class="comment-user" href="{{comment.user.get_profile_url}}">{{comment.user}}</a>
+ {% spaceless %}
+ <span class="comment-age">({% diff_date comment.added_at %})</span>
+ {% if request.user|can_delete_comment:comment %}
+ <img class="delete-icon"
+ src="{% href "/content/images/close-small.png" %}"
+ title="{% trans "delete this comment" %}"/>
+ {% endif %}
+ {% endspaceless %}
+ </p>
+ {% endfor %}
+ </div>
+ <div class="post-comments" style="margin-bottom:20px">
+ <input id="can-post-comments-answer-{{answer.id}}" type="hidden" value="{{ request.user|can_add_comments:answer}}"/>
+ {% if request.user|can_add_comments:answer or answer.comment_count > 5 %}
+ <a id="comments-link-answer-{{answer.id}}" class="comments-link">
+ {% if request.user|can_add_comments:answer %}
+ {% trans "add comment" %}
+ {% endif %}
+ {% if answer.comment_count > 5 %}
+ {% if request.user|can_add_comments:answer %}/
+ {% blocktrans count answer.get_comments|slice:"5:"|length as counter %}
+ see <strong>one</strong> more
+ {% plural %}
+ see <strong>{{counter}}</strong> more
+ {% endblocktrans %}
{% else %}
- <div class="answer-mark">
- <table width="200px">
- <tr>
- <td colspan="2">
- {% trans "asked" %} <strong title="{{answer.added_at}}">{% diff_date answer.added_at %}</strong>
- </td>
- </tr>
- <tr>
- <td width="40px" style="vertical-align:bottom">
- {% gravatar answer.author 32 %}
- </td>
- <td style="width:160px; vertical-align:top">
- <div><a href="{% url users %}{{ answer.author.id }}/{{ answer.author.username }}">{{ answer.author }}</a></div>
- <div>
- {% get_score_badge answer.author %}
- </div>
- </td>
- </tr>
- </table>
- </div>
+ {% blocktrans count answer.get_comments|slice:"5:"|length as counter %}
+ see <strong>one</strong> more comment
+ {% plural %}
+ see <strong>{{counter}}</strong> more comments
+ {% endblocktrans %}
{% endif %}
-
- </td>
- </tr>
-
- </table>
-
+ {% endif %}</a>
+ {% endif %}
</div>
-
</div>
<div id="comment-{{ answer.id }}" class="post-comments" >
<input id="can-post-comments-answer-{{answer.id}}" type="hidden" value="{{ request.user|can_add_comments }}"/>
@@ -440,46 +376,101 @@
<div class="paginator-container-left">
{% cnprog_paginator context %}
</div>
+ {% endif %}
+ <form id="fmanswer" action="{% url answer question.id %}" method="post">
+ {% if request.user.is_authenticated %}
+ <p style="padding-left:3px">
+ {{ answer.email_notify }}
+ <label for="question-subscribe-updates">
+ {% ifequal request.user.get_q_sel_email_feed_frequency 'n' %}
+ {% trans "Notify me once a day when there are any new answers" %}
+ {% else %}
+ {% ifequal request.user.get_q_sel_email_feed_frequency 'd' %}
+ {% trans "Notify me once a day when there are any new answers" %}
+ {% else %}
+ {% ifequal request.user.get_q_sel_email_feed_frequency 'w' %}
+ {% trans "Notify me weekly when there are any new answers" %}
+ {% endifequal %}
+ {% endifequal %}
+ {% endifequal %}
+ </label>
+ {% blocktrans with request.user.get_profile_url as profile_url %}
+ You can always adjust frequency of email updates from your {{profile_url}}
+ {% endblocktrans %}
+ </p>
{% else %}
- <div class="line"></div>
- {% endifnotequal %}
+ <p style="padding-left:3px">
+ <input class="nomargin" type="checkbox" disabled="disabled" />
+ <label>{% trans "once you sign in you will be able to subscribe for any updates here" %}</label>
+ </p>
+ {% endif %}
<div style="clear:both">
</div>
{% if not question.closed %}
- <div style="padding:10px 0 0 0;">
- <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>
- {{ answer.text }}
- <div class="preview-toggle">
- <table width="100%">
- <tr>
- <td>
- <span id="pre-collapse"
- title="{% trans "Toggle the real time Markdown editor preview" %}">{% trans "toggle preview" %}</span>
- </td>
- <td style="text-align:right;">
- {{ answer.wiki }} <span style="font-weight:normal;cursor:help" title="{{answer.wiki.help_text}}">{{ answer.wiki.label_tag }} </span>
- </td>
- </tr>
-
- </table>
+ <div style="padding:10px 0 0 0;">
+ {% spaceless %}
+ <div class="headNormal">
+ {% if answers %}
+ {% trans "Your answer" %}
+ {% else %}
+ {% trans "Be the first one to answer this question!" %}
+ {% endif %}
+ </div>
+ {% endspaceless %}
</div>
- <div id="previewer" class="wmd-preview"></div>
- {{ answer.text.errors }}
- </div>
- <input type="submit" value="{% trans "Answer the question" %}" class="submit"/><span class="form-error"></span>
- {% if request.user.is_authenticated %}
- {{ answer.email_notify }} <label for="question-subscribe-updates">{% trans "Notify me daily if there are any new answers." %}</label>
+ {% if not request.user.is_authenticated %}
+ <div class="message">{% trans "you can answer anonymously and then login" %}</div>
{% else %}
- <input type="checkbox" disabled="disabled" /><label>{% trans "once you sign in you will be able to subscribe for any updates here" %}</label>
+ <p class="message">
+ {% ifequal request.user question.author %}
+ {% trans "answer your own question only to give an answer" %}
+ {% else %}
+ {% trans "please only give an answer, no discussions" %}
+ {% endifequal %}
+ </p>
{% endif %}
+
+ <div id="description" class="" >
+ <div id="wmd-button-bar" class="wmd-panel"></div>
+ {{ answer.text }}
+ <div class="preview-toggle">
+ <table width="100%">
+ <tr>
+ <td>
+ <span id="pre-collapse"
+ title="{% trans "Toggle the real time Markdown editor preview" %}">
+ {% trans "toggle preview" %}
+ </span>
+ </td>
+ {% if settings.WIKI_ON %}
+ <td style="text-align:right;">
+ {{ answer.wiki }}
+ <span style="font-weight:normal;cursor:help"
+ title="{{answer.wiki.help_text}}">
+ {{ answer.wiki.label_tag }}
+ </span>
+ </td>
+ {% endif %}
+ </tr>
+
+ </table>
+ </div>
+ <div id="previewer" class="wmd-preview"></div>
+ {{ answer.text.errors }}
+ </div>
+ <p><span class="form-error"></span></p>
+ <input type="submit"
+ {% if user.is_anonymous %}
+ value="{% trans "Login/Signup to Post Your Answer" %}"
+ {% else %}
+ {% if user == question.author %}
+ value="{% trans "Answer Your Own Question" %}"
+ {% else %}
+ value="{% trans "Answer the question" %}"
+ {% endif %}
+ {% endif %}
+ class="submit" style="float:left"/>
{% endif %}
</form>
</div>
@@ -495,17 +486,17 @@
{% for tag in tags %}
<a href="{% url forum.views.tag tag.name|urlencode %}"
title="{% trans "see questions tagged"%}'{{tag.name}}'{% trans "using tags" %}"
- rel="tag">{{ tag.name }}</a> <span class="tag-number">&#x2715;{{ tag.used_count|intcomma }}</span><br/>
+ rel="tag">{{ tag.name }}</a> <span class="tag-number">&#215;{{ tag.used_count|intcomma }}</span><br/>
{% endfor %}
</p>
<p>
- {% trans "question asked" %}: <strong title="{{ question.added_at }}">{{ question.added_at|timesince }} {% trans "ago" %}</strong>
+ {% trans "question asked" %}: <strong title="{{ question.added_at }}">{% diff_date question.added_at %}</strong>
</p>
<p>
{% trans "question was seen" %}: <strong>{{ question.view_count|intcomma }} {% trans "times" %}</strong>
</p>
<p>
- {% trans "last updated" %}: <strong title="{{ question.last_activity_at }}">{{ question.last_activity_at|timesince }} {% trans "ago" %}</strong>
+ {% trans "last updated" %}: <strong title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</strong>
</p>
</div>
@@ -514,7 +505,7 @@
<div class="questions-related">
{% for question in similar_questions %}
<p>
- <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
+ <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
</p>
{% endfor %}
</div>
diff --git a/templates/question_edit.html b/templates/question_edit.html
index a7460b65..7a10b6ae 100644
--- a/templates/question_edit.html
+++ b/templates/question_edit.html
@@ -1,14 +1,15 @@
{% extends "base.html" %}
<!-- question_edit.html -->
{% load i18n %}
+{% load extra_tags %}
{% block title %}{% spaceless %}{% trans "Edit question" %}{% 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>
- <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
- <script type='text/javascript' src='/content/js/wmd/showdown.js'></script>
- <script type='text/javascript' src='/content/js/wmd/wmd.js'></script>
- <link rel="stylesheet" type="text/css" href="/content/js/wmd/wmd.css" />
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.post.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/showdown.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/wmd.js" %}'></script>
+ <link rel="stylesheet" type="text/css" href="{% href "/content/js/wmd/wmd.css" %}" />
<script type="text/javascript">
//todo move javascript out
$().ready(function(){
@@ -21,7 +22,7 @@
//toggle preview of editor
var display = true;
- var txt = "[{% trans "hide preview"}%]";
+ var txt = "[{% trans "hide preview" %}]";
$('#pre-collapse').text(txt);
$('#pre-collapse').bind('click', function(){
txt = display ? "[{% trans "show preview" %}]" : "[{% trans "hide preview" %}]";
@@ -90,9 +91,11 @@
<td>
<span id="pre-collapse" title="{% trans "Toggle the real time Markdown editor preview" %}">{% trans "toggle preview" %}</span>
</td>
+ {% if settings.WIKI_ON %}
<td style="text-align:right;">
{{ form.wiki }} <span style="color:#000;cursor:help" title="{{form.wiki.help_text}}">{{ form.wiki.label_tag }} </span>
</td>
+ {% endif %}
</tr>
</table>
diff --git a/templates/question_edit_tips.html b/templates/question_edit_tips.html
index 85614595..4cabea79 100644
--- a/templates/question_edit_tips.html
+++ b/templates/question_edit_tips.html
@@ -13,8 +13,9 @@
{% trans "be clear and concise" %}
</li>
</ul>
- <a href="{% url faq %}" target="_blank" title="{% trans "see frequently asked questions" %}" style="float:right;position:relative">{% trans "faq" %} »</a>
- <br>
+ <p class='info-box-follow-up-links'>
+ <a href="{% url faq %}" target="_blank" title="{% trans "see frequently asked questions" %}">{% trans "faq" %} »</a>
+ </p>
</div>
</div>
@@ -45,6 +46,8 @@
{% 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>
+ <p class='info-box-follow-up-links'>
+ <a href="http://en.wikipedia.org/wiki/Markdown" target="_blank">{% trans "learn more about Markdown" %} »</a>
+ </p>
</div>
<!-- end question_edit_tips.html -->
diff --git a/templates/question_retag.html b/templates/question_retag.html
index 66e51c8c..b7957962 100644
--- a/templates/question_retag.html
+++ b/templates/question_retag.html
@@ -1,10 +1,11 @@
{% extends "base.html" %}
<!-- question_retag.html -->
+{% load extra_tags %}
{% 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>
- <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.post.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
<script type="text/javascript">
$().ready(function(){
@@ -84,14 +85,18 @@
<li>
{% trans "tags help us keep Questions organized" %}
</li>
+ {% comment %}
<li>
修改完整问题需要用户的积分达到一定条件(比如:积分 >= 3000分,自己发布的问题除外),而用户积分达到比较低的时候,就可以修改问题的标签(比如:积分 >= 500, 这里指所有问题的标签)。
</li>
+ {% endcomment %}
<li>
{% trans "tag editors receive special awards from the community" %}
</li>
</ul>
- <a href="{% url faq %}" style="float:right;position:relative">faq »</a>
+ <p class='info-box-follow-up-links'>
+ <a href="{% url faq %}">faq »</a>
+ </p>
</div>
{% endblock %}
diff --git a/templates/question_summary_list_roll.html b/templates/question_summary_list_roll.html
new file mode 100644
index 00000000..7312dca9
--- /dev/null
+++ b/templates/question_summary_list_roll.html
@@ -0,0 +1,55 @@
+ <div class="qstA">
+ <h2>
+ <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
+ </h2>
+ <div class="stat">
+ <table>
+ <tr>
+ <td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
+ <td><span class="num">{{ question.score|intcomma }}</span> </td>
+ <td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
+ </tr>
+ <tr>
+ <td><span class="unit">{% trans "answers" %}</span></td>
+ <td><span class="unit">{% trans "votes" %}</span></td>
+ <td><span class="unit">{% trans "views" %}</span></td>
+ </tr>
+ </table>
+ </div>
+
+ <div class="summary">
+ {{ question.summary }}...
+ </div>
+
+ {% ifequal tab_id 'active'%}
+ {% if question.wiki and settings.WIKI_ON %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ </div>
+ {% endif %}
+ {% else %}
+ {% if question.wiki and settings.WIKI_ON %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.author 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ </div>
+ {% endif %}
+ {% endifequal %}
+
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ </div>
diff --git a/templates/questions.html b/templates/questions.html
index 6121d876..f74256cf 100644
--- a/templates/questions.html
+++ b/templates/questions.html
@@ -4,23 +4,49 @@
{% load i18n %}
{% load humanize %}
{% load extra_filters %}
+{% load smart_if %}
{% block title %}{% spaceless %}{% trans "Questions" %}{% endspaceless %}{% endblock %}
{% block forejs %}
<script type="text/javascript">
- $().ready(function(){
- var tab_id = "{{ tab_id }}";
- $("#"+tab_id).attr('className',"on");
- $("#nav_questions").attr('className',"on");
- Hilite.exact = false;
- Hilite.elementid = "listA";
- Hilite.debug_referrer = location.href;
- });
-
- </script>
+ var tags = {{ tags_autocomplete|safe }};
+ $().ready(function(){
+ var tab_id = "{{ tab_id }}";
+ $("#"+tab_id).attr('className',"on");
+ var on_tab = {% if is_unanswered %}'#nav_unanswered'{% else %}'#nav_questions'{% endif %};
+ $(on_tab).attr('className','on');
+ Hilite.exact = false;
+ Hilite.elementid = "listA";
+ Hilite.debug_referrer = location.href;
+ });
+ </script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.tag_selector.js" %}'></script>
{% endblock %}
{% block content %}
<div class="tabBar">
+<<<<<<< HEAD:templates/questions.html
<div class="headQuestions">{% if searchtag %}{% trans "Found by tags" %}{% else %}{% if searchtitle %}{% trans "Found by title" %}{% else %}{% trans "All questions" %}{% endif %}{% endif %}</div>
+=======
+ <div class="headQuestions">
+ {% if searchtag %}
+ {% trans "Found by tags" %}
+ {% else %}
+ {% if searchtitle %}
+ {% if settings.USE_SPHINX_SEARCH %}
+ {% trans "Search results" %}
+ {% else %}
+ {% trans "Found by title" %}
+ {% endif %}
+ {% else %}
+ {% if is_unanswered %}
+ {% trans "Unanswered questions" %}
+ {% else %}
+ {% trans "All questions" %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ </div>
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/questions.html
<div class="tabsA">
<a id="latest" href="?sort=latest" class="off" title="{% trans "most recently asked questions" %}">{% trans "newest" %}</a>
<a id="active" href="?sort=active" class="off" title="{% trans "most recently updated questions" %}">{% trans "active" %}</a>
@@ -30,22 +56,34 @@
</div>
<div id="listA">
{% for question in questions.object_list %}
- <div class="qstA">
+ <div class="qstA"
+ {% if request.user.is_authenticated %}
+ {% if question.interesting_score > 0 %}
+ style="background:#ffff99;"
+ {% else %}
+ {% if not request.user.hide_ignored_questions %}
+ {% if question.ignored_score > 0 %}
+ style="background:#f3f3f3;"
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ >
<h2>
<a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
</h2>
<div class="stat">
<table>
- <tr>
+ <tr>
<td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
<td><span class="num">{{ question.score|intcomma }}</span> </td>
<td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
- </tr>
+ </tr>
<tr>
<td><span class="unit">{% trans "answers" %}</span></td>
<td><span class="unit">{% trans "votes" %}</span></td>
<td><span class="unit">{% trans "views" %}</span></td>
- </tr>
+ </tr>
</table>
</div>
@@ -54,7 +92,7 @@
</div>
{% ifequal tab_id 'active'%}
- {% if question.wiki %}
+ {% if question.wiki and settings.WIKI_ON %}
<span class="from wiki">{% trans "community wiki" %}</span>
<span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
{% else %}
@@ -66,15 +104,33 @@
</div>
{% endif %}
{% else %}
- {% if question.wiki %}
+ {% if question.wiki and settings.WIKI_ON %}
<span class="from wiki">{% trans "community wiki" %}</span>
<span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
{% else %}
<div class="from">
{% comment %}{% gravatar question.author 24 %}{% endcomment %}
- <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
- <span class="score">{% get_score_badge question.author %} </span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% if question.last_activity_at != question.added_at %}
+ {% if question.author.id != question.last_activity_by.id %}
+ {% trans "Posted:" %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ / {% trans "Updated:" %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ {% else %}
+ {% trans "Updated:" %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ {% endif %}
+ {% else %}
+ {% trans "Posted:" %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% endif %}
</div>
{% endif %}
{% endifequal %}
@@ -87,26 +143,29 @@
{%trans "Category: "%}<a href="{% url forum.views.category question.category|urlencode %}">{{ question.category}}</a>
</div>
{% endfor %}
+ {% if searchtitle %}
+ {% if questions_count == 0 %}
+ <p class="evenMore" style="padding-top:30px;text-align:center;">
+ {% trans "Did not find anything?" %}
+ {% else %}
+ <p class="evenMore" style="padding-left:9px">
+ {% trans "Did not find what you were looking for?" %}
+ {% endif %}
+ <a href="{% url ask %}">{% trans "Please, post your question!" %}</a>
+ </p>
+ {% endif %}
</div>
-
{% endblock %}
{% block tail %}
-
- <div class="pager">
- {% cnprog_paginator context %}
-
- </div>
- <div class="pagesize">
- {% cnprog_pagesize context %}
- </div>
-
+ <div class="pager">{% cnprog_paginator context %}</div>
+ <div class="pagesize">{% cnprog_pagesize context %}</div>
{% endblock %}
{% block sidebar %}
<div class="boxC">
- <p>
{% if searchtag %}
+<<<<<<< HEAD:templates/questions.html
{% blocktrans count questions_count as cnt with questions_count|intcomma as q_num and searchtag as tagname %}
have total {{q_num}} questions tagged {{tagname}}
{% plural %}
@@ -127,7 +186,46 @@
{% endblocktrans %}
{% endif %}
{% endif %}
- <br/>
+ <p>
+=======
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num and searchtag as tagname %}
+ have total {{q_num}} questions tagged {{tagname}}
+ {% plural %}
+ have total {{q_num}} questions tagged {{tagname}}
+ {% endblocktrans %}
+ {% else %}
+ {% if searchtitle %}
+ {% if settings.USE_SPHINX_SEARCH %}
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %}
+ have total {{q_num}} questions containing {{searchtitle}} in full text
+ {% plural %}
+ have total {{q_num}} questions containing {{searchtitle}} in full text
+ {% endblocktrans %}
+ {% else %}
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %}
+ have total {{q_num}} questions containing {{searchtitle}}
+ {% plural %}
+ have total {{q_num}} questions containing {{searchtitle}}
+ {% endblocktrans %}
+ {% endif %}
+ {% else %}
+ {% if is_unanswered %}
+ {% blocktrans count questions as cnt with questions_count|intcomma as q_num %}
+ have total {{q_num}} unanswered questions
+ {% plural %}
+ have total {{q_num}} unanswered questions
+ {% endblocktrans %}
+ {% else %}
+ {% blocktrans count questions as cnt with questions_count|intcomma as q_num %}
+ have total {{q_num}} questions
+ {% plural %}
+ have total {{q_num}} questions
+ {% endblocktrans %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ <p class="nomargin">
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/questions.html
{% ifequal tab_id "latest" %}
{% trans "latest questions info" %}
{% endifequal %}
@@ -146,19 +244,27 @@
{% trans "Questions are sorted by the <strong>number of votes</strong>." %}
{% trans "Most voted questions are shown first." %}
{% endifequal %}
-
-
- </p>
+ </p>
</div>
+{% if request.user.is_authenticated %}
+{% include "tag_selector.html" %}
+{% endif %}
<div class="boxC">
<h3 class="subtitle">{% trans "Related tags" %}</h3>
<div class="tags">
{% for tag in tags %}
+<<<<<<< HEAD:templates/questions.html
<a rel="tag" title="{% trans "see questions tagged" %}'{{ tag.name }}'{% trans "using tags" %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
- <span class="tag-number">&#x2715; {{ tag.used_count|intcomma }}</span>
+ <span class="tag-number">&#215; {{ tag.used_count|intcomma }}</span>
<br />
{% endfor %}
<br />
+=======
+ <a rel="tag" title="{% blocktrans with tag.name as tag_name %}see questions tagged '{{ tag_name }}'{% endblocktrans %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
+ <span class="tag-number">&#215; {{ tag.used_count|intcomma }}</span>
+ <br />
+ {% endfor %}
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/questions.html
</div>
</div>
diff --git a/templates/revisions_answer.html b/templates/revisions_answer.html
index 23606dc9..974e589c 100644
--- a/templates/revisions_answer.html
+++ b/templates/revisions_answer.html
@@ -6,8 +6,8 @@
{% load humanize %}
{% block title %}{% spaceless %}{% trans "Revision history" %}{% 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>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.post.js" %}'></script>
<script type="text/javascript">
//todo - take this out into .js file
$().ready(function(){
@@ -24,7 +24,9 @@
var arrow = $("#rev-arrow-" + id);
var visible = arrow.attr("src").indexOf("hide") > -1;
- arrow.attr("src", "/content/images/expander-arrow-" + (visible ? "show" : "hide") + ".gif");
+ var path = $.i18n._('/') + "content/images/expander-arrow-" +
+ (visible ? "show" : "hide") + ".gif" + "?v={{settings.RESOURCE_REVISION}}";
+ arrow.attr("src", path);
$("#rev-body-" + id).slideToggle("fast");
}
@@ -43,11 +45,14 @@
<div class="header-controls">
<table width="100%">
<tr>
- <td width="20" style="vertical-align:middle"><img id="rev-arrow-{{ revision.revision }}" src="/content/images/expander-arrow-show.gif"></td>
+ <td width="20" style="vertical-align:middle"><img id="rev-arrow-{{ revision.revision }}"
+ src="{% href "/content/images/expander-arrow-show.gif" %}"
+ alt="{% trans "click to hide/show revision" %}"/>
+ </td>
<td width="30px" style="vertical-align:middle"><span class="revision-number" title="{% trans "revision" %} {{ revision.revision }}">{{ revision.revision }}</span></td>
<td width="200px" style="vertical-align:middle">
{% if revision.summary %}
- <div class="summary"><span>{{ revision.summary }}<span></div>
+ <div class="summary"><span>{{ revision.summary }}</span></div>
{% endif %}
{% if request.user|can_edit_post:post %}
<a href="{% url edit_answer post.id %}?revision={{ revision.revision }}">{% trans "edit" %}</a>
@@ -56,31 +61,7 @@
</td>
<td align="right">
<div class="revision-mark" >
- <table width="100%" style="text-align:left" >
- <tr >
- <td colspan="2" style="padding:3px 0 3px 0">
- {% ifequal revision.revision 1 %}
- {% trans "asked" %} <strong title="{{ post.added_at }}">{% diff_date post.added_at %}</strong>
- {% else %}
- {% trans "updated" %} <strong title="{{ post.last_edited_at }}">{% diff_date revision.revised_at %}</strong>
- {% endifequal %}
- </td>
-
- </tr>
- <tr>
- <td width="35px" style="vertical-align:bottom; ">
- {% gravatar revision.author 32 %}
- </td>
- <td style="width:120px; vertical-align:top">
- <div style="height:18px">
- <a href="{% url users %}{{ revision.author.id }}/{{ revision.author.username }}">{{ revision.author.username }}</a></div>
- <div>
- {% get_score_badge revision.author %}
- </div>
- </td>
- </tr>
-
- </table>
+ {% post_contributor_info revision %}
</div>
</td>
</tr>
diff --git a/templates/revisions_question.html b/templates/revisions_question.html
index b76ced24..83512e4a 100644
--- a/templates/revisions_question.html
+++ b/templates/revisions_question.html
@@ -7,8 +7,8 @@
{% load humanize %}
{% block title %}{% spaceless %}{% trans "Revision history" %}{% 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>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.post.js" %}'></script>
<script type="text/javascript">
//todo - take this out into .js file
$().ready(function(){
@@ -25,7 +25,9 @@
var arrow = $("#rev-arrow-" + id);
var visible = arrow.attr("src").indexOf("hide") > -1;
- arrow.attr("src", "/content/images/expander-arrow-" + (visible ? "show" : "hide") + ".gif");
+ var path = $.i18n._('/') + "content/images/expander-arrow-" +
+ (visible ? "show" : "hide") + ".gif" + "?v={{settings.RESOURCE_REVISION}}";
+ arrow.attr("src", path);
$("#rev-body-" + id).slideToggle("fast");
}
</script>
@@ -43,11 +45,14 @@
<div class="header-controls">
<table width="100%">
<tr>
- <td width="20" style="vertical-align:middle"><img id="rev-arrow-{{ revision.revision }}" src="/content/images/expander-arrow-show.gif"></td>
+ <td width="20" style="vertical-align:middle"><img id="rev-arrow-{{ revision.revision }}"
+ src="{% href "/content/images/expander-arrow-show.gif" %}"
+ alt="{% trans "click to hide/show revision" %}"/>
+ </td>
<td width="30px" style="vertical-align:middle"><span class="revision-number" title="{% trans "revision" %} {{ revision.revision }}">{{ revision.revision }}</span></td>
<td width="200px" style="vertical-align:middle">
{% if revision.summary %}
- <div class="summary"><span>{{ revision.summary }}<span></div>
+ <div class="summary"><span>{{ revision.summary }}</span></div>
{% endif %}
{% if request.user|can_edit_post:post %}
<a href="{% url edit_question post.id %}?revision={{ revision.revision }}">{% trans "edit" %}</a>
@@ -56,31 +61,7 @@
</td>
<td align="right">
<div class="revision-mark" >
- <table width="100%" style="text-align:left" >
- <tr >
- <td colspan="2" style="padding:3px 0 3px 0">
- {% ifequal revision.revision 1 %}
- {% trans "asked" %}<strong title="{{ post.added_at }}">{% diff_date post.added_at %}</strong>
- {% else %}
- {% trans "updated" %}<strong title="{{ post.last_edited_at }}">{% diff_date revision.revised_at %}</strong>
- {% endifequal %}
- </td>
-
- </tr>
- <tr>
- <td width="35px" style="vertical-align:bottom; ">
- {% gravatar revision.author 32 %}
- </td>
- <td style="width:120px; vertical-align:top">
- <div style="height:18px">
- <a href="{% url users %}{{ revision.author.id }}/{{ revision.author.username }}">{{ revision.author.username }}</a></div>
- <div>
- {% get_score_badge revision.author %}
- </div>
- </td>
- </tr>
-
- </table>
+ {% post_contributor_info revision %}
</div>
</td>
</tr>
diff --git a/templates/tag_selector.html b/templates/tag_selector.html
new file mode 100644
index 00000000..6edc5cc8
--- /dev/null
+++ b/templates/tag_selector.html
@@ -0,0 +1,42 @@
+{% load i18n %}
+{% load extra_tags %}
+<div id="tagSelector" class="boxC">
+ <h3 class="subtitle">{% trans "Interesting tags" %}</h3>
+ <div class="tags interesting marked-tags">
+ {% for tag_name in interesting_tag_names %}
+ {% spaceless %}
+ <span class="deletable-tag" id="interesting-tag-{{tag_name}}">
+ <a rel="tag"
+ title="{% blocktrans with tag as tagname %}see questions tagged '{{ tag_name }}'{% endblocktrans %}"
+ href="{% url tag_questions tag_name|urlencode %}">{{tag_name}}</a>
+ <img class="delete-icon"
+ src="{% href "/content/images/close-small-dark.png" %}"
+ title="{% blocktrans %}remove '{{tag_name}}' from the list of interesting tags{% endblocktrans %}"/>
+ </span>
+ {% endspaceless %}
+ {% endfor %}
+ </div>
+ <input id="interestingTagInput" autocomplete="off" type="text"/>
+ <input id="interestingTagAdd" type="submit" value="{% trans "Add" %}"/>
+ <h3 class="subtitle">{% trans "Ignored tags" %}</h3>
+ <div class="tags ignored marked-tags">
+ {% for tag_name in ignored_tag_names %}
+ {% spaceless %}
+ <span class="deletable-tag" id="ignored-tag-{{tag_name}}">
+ <a rel="tag"
+ title="{% blocktrans with tag as tagname %}see questions tagged '{{ tag_name }}'{% endblocktrans %}"
+ href="{% url tag_questions tag_name|urlencode %}">{{tag_name}}</a>
+ <img class="delete-icon"
+ src="{% href "/content/images/close-small-dark.png" %}"
+ title="{% blocktrans %}remove '{{tag_name}}' from the list of ignored tags{% endblocktrans %}"/>
+ </span>
+ {% endspaceless %}
+ {% endfor %}
+ </div>
+ <input id="ignoredTagInput" autocomplete="off" type="text"/>
+ <input id="ignoredTagAdd" type="submit" value="{% trans "Add" %}"/>
+ <p id="hideIgnoredTagsControl">
+ <input id="hideIgnoredTagsCb" type="checkbox" {% if request.user.hide_ignored_questions %}checked="checked"{% endif %} />
+ <label id="hideIgnoredTagsLabel" for="hideIgnoredTags">{% trans "keep ingored questions hidden" %}</label>
+ <p>
+</div>
diff --git a/templates/tags.html b/templates/tags.html
index f558594a..1bde187f 100644
--- a/templates/tags.html
+++ b/templates/tags.html
@@ -49,7 +49,7 @@
<a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">
{{ tag }}
</a>&nbsp;
- <span class="tag-number">× {{ tag.used_count|intcomma }}</span>
+ <span class="tag-number">&#215; {{ tag.used_count|intcomma }}</span>
<br/>
</li>
diff --git a/templates/unanswered.html b/templates/unanswered.html
index a56d3ae3..80a23631 100644
--- a/templates/unanswered.html
+++ b/templates/unanswered.html
@@ -4,6 +4,7 @@
{% load i18n %}
{% load humanize %}
{% load extra_filters %}
+{% load smart_if %}
{% block title %}{% spaceless %}{% trans "Unanswered questions" %}{% endspaceless %}{% endblock %}
{% block forejs %}
<script type="text/javascript">
@@ -17,65 +18,82 @@
<div class="tabBar">
<span class="headQuestions">{% trans "Unanswered questions" %}</span>
<div class="tabsA">
- <a id="latest" href="?sort=latest" class="on" title="{% trans "most recently asked questions" %}">{% trans "newest" %}</a>
+ <a id="latest" href="{% url unanswered %}?sort=latest" class="on" title="{% trans "most recently asked questions" %}">{% trans "newest" %}</a>
</div>
</div>
-
<div id="listA">
{% for question in questions.object_list %}
<div class="qstA">
- <h2><a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a></h2>
+ <h2>
+ <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
+ </h2>
<div class="stat">
<table>
- <tr>
+ <tr>
<td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
<td><span class="num">{{ question.score|intcomma }}</span> </td>
<td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
- </tr>
+ </tr>
<tr>
<td><span class="unit">{% trans "answers" %}</span></td>
<td><span class="unit">{% trans "votes" %}</span></td>
<td><span class="unit">{% trans "views" %}</span></td>
- </tr>
+ </tr>
</table>
</div>
+
<div class="summary">
{{ question.summary }}...
</div>
-
+
{% ifequal tab_id 'active'%}
- {% if question.wiki %}
+ {% if question.wiki and settings.WIKI_ON %}
<span class="from wiki">{% trans "community wiki" %}</span>
<span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
{% else %}
<div class="from">
- {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
+ {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
<span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
<span class="score">{% get_score_badge question.last_activity_by %} </span>
<span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
</div>
{% endif %}
{% else %}
- {% if question.wiki %}
+ {% if question.wiki and settings.WIKI_ON %}
<span class="from wiki">{% trans "community wiki" %}</span>
<span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
{% else %}
<div class="from">
- {% comment %}{% gravatar question.author 24 %}{% endcomment %}
- <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
- <span class="score">{% get_score_badge question.author %} </span>
- <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% comment %}{% gravatar question.author 24 %}{% endcomment %}
+ {% if question.last_activity_at != question.added_at %}
+ {% if question.author.id != question.last_activity_by.id %}
+ {% trans "Posted:" %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ / {% trans "Updated:" %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ {% else %}
+ {% trans "Updated:" %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ {% endif %}
+ {% else %}
+ {% trans "Posted:" %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% endif %}
</div>
{% endif %}
{% endifequal %}
<div class="tags">
- {% for tag in question.tagname_list %}
- <a href="{% url forum.views.tag tag|urlencode %}"
- title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}"
- rel="tag">{{ tag }}
- </a>
- {% endfor %}
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
</div>
</div>
{% endfor %}
@@ -93,10 +111,8 @@
{% block sidebar %}
<div class="boxC">
- <p>
- {% blocktrans with questions_count|intcomma as num_q %}have {{num_q}} unanswered questions{% endblocktrans %}
- <!---<p>问题按 <strong>问题创建时间</strong> 排序。最新加入的问题将显示在最前面。</p>-->
- </p>
+ {% blocktrans with questions_count|intcomma as num_q %}have {{num_q}} unanswered questions{% endblocktrans %}
+ <!---<p>问题按 <strong>问题创建时间</strong> 排序。最新加入的问题将显示在最前面。</p>-->
</div>
<div class="boxC">
<h3 class="subtitle">{% trans "Related tags" %}</h3>
@@ -104,7 +120,7 @@
<div class="tags">
{% for tag in tags %}
<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"> &#x2715; {{ tag.used_count|intcomma }}</span>
+ <span class="tag-number"> &#215; {{ tag.used_count|intcomma }}</span>
<br/>
{% endfor %}
<br/>
diff --git a/templates/upfiles/1245715031297631.png b/templates/upfiles/1245715031297631.png
deleted file mode 100755
index 89a6aed4..00000000
--- a/templates/upfiles/1245715031297631.png
+++ /dev/null
Binary files differ
diff --git a/templates/upfiles/12457157052552259.png b/templates/upfiles/12457157052552259.png
deleted file mode 100755
index 89a6aed4..00000000
--- a/templates/upfiles/12457157052552259.png
+++ /dev/null
Binary files differ
diff --git a/templates/user.html b/templates/user.html
index efca80e6..6e4098e9 100644
--- a/templates/user.html
+++ b/templates/user.html
@@ -1,6 +1,7 @@
{% extends "base_content.html" %}
<!-- user.html -->
{% load extra_tags %}
+{% load extra_filters %}
{% load humanize %}
{% block title %}{% spaceless %}{{ page_title }}{% endspaceless %}{% endblock %}
{% block forestyle%}
@@ -10,17 +11,22 @@
</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 %}
+ {% if request.user|can_moderate_users %}
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.admin.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.form.js" %}'></script>
+ {% endif %}
+ <script type="text/javascript">
+ var viewUserID = {{view_user.id}};
+ $().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">
@@ -28,6 +34,6 @@
{% include "user_tabs.html" %}
{% block usercontent %}
{% endblock %}
- {% include "user_footer.html" %}
+ {%comment%}{% include "user_footer.html" %}{%endcomment%}
</div>
{% endblock %}<!-- end user.html -->
diff --git a/templates/user_edit.html b/templates/user_edit.html
index b49cea31..5886c071 100644
--- a/templates/user_edit.html
+++ b/templates/user_edit.html
@@ -24,11 +24,11 @@
{% if request.user.email %}
{% gravatar request.user 128 %}
{% else %}
- <img src="/content/images/nophoto.png">
+ <img src="{% href "/content/images/nophoto.png" %}">
{% endif %}
<div style="padding:20px 0 0 20px;font-weight:bold;font-size:150%">
<a href="http://www.gravatar.com/" target="_blank"
- title="gravatar {% trans "image associated with your email address" %}">{% trans "avatar" %}</a>
+ title="gravatar {% trans "image associated with your email address" %}">{% blocktrans %}avatar, see {{gravatar_faq_url}}{% endblocktrans %}</a>
</div>
</div>
@@ -39,6 +39,10 @@
<th width="100px"></th>
<th></th>
</tr>
+ <tr style="height:35px">
+ <td>{{ form.username.label_tag }}:</td>
+ <td>{{ form.username }} <span class="form-error"></span> {{ form.username.errors }} </td>
+ </tr>
<tr style="height:35px">
<td>{{ form.email.label_tag }}:</td>
diff --git a/templates/user_email_subscriptions.html b/templates/user_email_subscriptions.html
new file mode 100644
index 00000000..10440529
--- /dev/null
+++ b/templates/user_email_subscriptions.html
@@ -0,0 +1,29 @@
+{% extends "user.html" %}
+<!-- user_email_subscriptions.html -->
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+
+{% block usercontent %}
+ <h2>{% trans "Email subscription settings" %}</h2>
+ <p class="message">{% trans "email subscription settings info" %}</p>
+ <div class='inline-block'>
+ {% if action_status %}
+ <p class="action-status"><span>{{action_status}}</span></p>
+ {% endif %}
+ <form method="POST">
+ {% include "edit_user_email_feeds_form.html" %}
+<<<<<<< HEAD:templates/user_email_subscriptions.html
+=======
+ <table class='form-as-table'>
+ {{tag_filter_selection_form}}
+ </table>
+>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/user_email_subscriptions.html
+ <div class="submit-row text-align-right">
+ <input type="submit" class="submit" name="save" value="{% trans "Update" %}"/>
+ <input type="submit" class="submit" name="stop_email" value="{% trans "Stop sending email" %}"/>
+ </div>
+ </form>
+ </div>
+{% endblock %}
+<!-- end user_email_subscriptions.html -->
diff --git a/templates/user_favorites.html b/templates/user_favorites.html
index 185423c6..9db01e9a 100644
--- a/templates/user_favorites.html
+++ b/templates/user_favorites.html
@@ -2,7 +2,6 @@
<!-- user_favorites.html -->
{% load extra_tags %}
{% load humanize %}
-
{% block usercontent %}
{% include "users_questions.html" %}
{% endblock %}
diff --git a/templates/user_info.html b/templates/user_info.html
index e56fb143..4ebcddd6 100644
--- a/templates/user_info.html
+++ b/templates/user_info.html
@@ -2,12 +2,11 @@
{% load extra_tags %}
{% load extra_filters %}
{% load humanize %}
+{% load smart_if %}
{% load i18n %}
<div id="subheader" class="headUser">
{{view_user.username}}
-
</div>
-
<table class="user-info-table">
<tr>
<td width="180" style="vertical-align:middle;text-align:center;">
@@ -20,20 +19,41 @@
<tr>
<td align="center">
<div class="scoreNumber">{{view_user.reputation|intcomma}}</div>
- <p><b style="color:#777;">{% trans "reputation" %}</b></p>
+ <p><b style="color:#777;">{% trans "karma" %}</b></p>
</td>
</tr>
</table>
</td>
<td width="360" style="vertical-align: top;">
<table class="user-details">
+ {% if view_user != request.user and request.user|can_moderate_users %}
<tr>
- <th width="130" align="left"><strong>{% trans "Registered user" %}</strong></th>
- <th width="230" align="right">
- {% if request.user|can_view_user_edit:view_user %}
- <span class="user-edit-link"><a href="{% url users %}{{ view_user.id }}/{% trans "edit/" %}">{% trans "update profile" %}</a></span>
- {% endif %}
- </th>
+ <td class="admin" align="left" colspan="2">
+ <h3>{% trans "Moderate this user" %}</h3>
+ <form id="moderate_user_form" method="post">
+ {{moderate_user_form.as_p}}
+ </form>
+ <p id="action_status"></p>
+ </td>
+ </tr>
+ {% endif %}
+ {% if request.user|can_view_user_edit:view_user %}
+ <tr>
+ <td class="user-profile-tool-links" align="left" colspan="2">
+ {% joinitems using ' | ' %}
+ {% if request.user|can_view_user_edit:view_user %}
+ <span class="user-edit-link"><a href="{% url users %}{{ view_user.id }}/{% trans "edit/" %}">{% trans "update profile" %}</a></span>
+ {% endif %}
+ {% separator %}
+ {% if request.user.has_usable_password %}
+ <a href="{% url user_changepw %}">change password</a>
+ {% endif %}
+ {% endjoinitems %}
+ </td>
+ </tr>
+ {% endif %}
+ <tr>
+ <th colspan="2" align="left"><h3>{% trans "Registered user" %}</h3></th>
</tr>
{% if view_user.real_name %}
<tr>
@@ -43,12 +63,12 @@
{% endif %}
<tr>
<td>{% trans "member for" %}</td>
- <td>{{ view_user.date_joined|timesince }}</td>
+ <td><strong>{% diff_date view_user.date_joined %}</strong></td>
</tr>
{% if view_user.last_seen %}
<tr>
<td>{% trans "last seen" %}</td>
- <td><strong title="{{ view_user.last_seen }}">{{ view_user.last_seen|timesince }} {% trans "ago" %}</strong></td>
+ <td><strong title="{{ view_user.last_seen }}">{% diff_date view_user.last_seen %}</strong></td>
</tr>
{% endif %}
{% if view_user.website %}
diff --git a/templates/user_preferences.html b/templates/user_preferences.html
deleted file mode 100644
index 00129c28..00000000
--- a/templates/user_preferences.html
+++ /dev/null
@@ -1,24 +0,0 @@
-{% extends "user.html" %}
-<!-- user_preferences.html -->
-{% load i18n %}
-{% load extra_tags %}
-{% load humanize %}
-
-{% block usercontent %}
- <div style="padding:5px;">
- <fieldset>
- <legend><b>{% trans "Connect with Twitter" %}</b></legend>
- <!-- todo: form action needs to be fixed -->
- <form name="twittersync" action="/sdfgsdlgjkhsdfljh">
- <label for="name">{% trans "Twitter account name:" %}</label>
- <input id="name" /><br/>
- <label for="password">{% trans "Twitter password:" %}</label>
- <input id="password" type="password"/><br/>
- <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 7f6c9c8c..b704ab25 100644
--- a/templates/user_recent.html
+++ b/templates/user_recent.html
@@ -2,7 +2,6 @@
<!-- user_recent.html -->
{% load extra_tags %}
{% load humanize %}
-
{% block usercontent %}
<div style="padding-top:5px;font-size:13px;">
{% for act in activities %}
diff --git a/templates/user_reputation.html b/templates/user_reputation.html
index 5b0e542b..16127140 100644
--- a/templates/user_reputation.html
+++ b/templates/user_reputation.html
@@ -1,10 +1,11 @@
{% extends "user.html" %}
<!-- user_reputation.html -->
{% load extra_tags %}
+{% load extra_filters %}
{% load humanize %}
{% block userjs %}
- <script type='text/javascript' src='/content/js/excanvas.pack.js'></script>
- <script type='text/javascript' src='/content/js/jquery.flot.pack.js'></script>
+ <script type='text/javascript' src='{% href "/content/js/excanvas.pack.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.flot.pack.js" %}'></script>
<script type="text/javascript">
$().ready(function(){
@@ -33,7 +34,7 @@
<div style="float:left;width:20px;color:red">{{ rep.negative }}</div>
</div>
- <a href="{% url questions %}{{ rep.question_id }}/{{ rep.title|slugify }}">{{ rep.title }}</a> <span class="small">({{ rep.reputed_at }})</span>
+ <a href="{% url question rep.question_id %}{{ rep.title|slugify }}">{{ rep.title }}</a> <span class="small">({{ rep.reputed_at }})</span>
</p>
{% endfor %}
</div>
diff --git a/templates/user_stats.html b/templates/user_stats.html
index b9c33e9f..ecc39807 100644
--- a/templates/user_stats.html
+++ b/templates/user_stats.html
@@ -7,39 +7,74 @@
{% block usercontent %}
<a name="questions"></a>
- <h2><span class="count">{{questions|length}}</span> {% trans "User questions" %}</h2>
+ {% spaceless %}
+ <h2>
+ {% blocktrans count questions|length as counter %}
+ <span class="count">1</span> Question
+ {% plural %}
+ <span class="count">{{counter}}</span> Questions
+ {% endblocktrans %}
+ </h2>
+ {% endspaceless %}
{% include "users_questions.html" %}
<a name="answers"></a>
- <h2><span class="count">{{answered_questions|length}}</span> {% trans "Answers" %}</h2>
+ {% spaceless %}
+ <h2>
+ {% blocktrans count answered_questions|length as counter %}
+ <span class="count">1</span> Answer
+ {% plural %}
+ <span class="count">{{counter}}</span> Answers
+ {% endblocktrans %}
+ </h2>
+ {% endspaceless %}
<div class="user-stats-table">
{% for answered_question in answered_questions %}
<div class="answer-summary">
<a title="{{answered_question.summary|collapse}}"
- href="{% url questions %}{{answered_question.id}}/{{answered_question.title}}#{{answered_question.answer_id}}">
+ href="{% url question answered_question.id %}{{answered_question.title|slugify}}#{{answered_question.answer_id}}">
<span class="answer-votes {% if answered_question.accepted %}answered-accepted{% endif %}"
title="{% blocktrans with answered_question.vote_count as vote_count %}the answer has been voted for {{ vote_count }} times{% endblocktrans %} {% if answered_question.accepted %}{% trans "this answer has been selected as correct" %}{%endif%}">
{{ answered_question.vote_count }}
</span>
</a>
<div class="answer-link">
- <a href="/questions/{{answered_question.id}}/{{answered_question.title}}#{{answered_question.answer_id}}">{{answered_question.title}}</a> {% if answered_question.comment_count %}<span
- title="{% blocktrans with answered_question.comment_count as count %}the answer has been commented {{count}} times{% endblocktrans %}">({{answered_question.comment_count}})</span>{% endif %}
+ {% spaceless %}
+ <a href="{% url question answered_question.id %}{{answered_question.title|slugify}}#{{answered_question.answer_id}}">{{answered_question.title}}</a>
+ {% endspaceless %}
+ {% if answered_question.comment_count %}
+ <span>
+ {% blocktrans count answered_question.comment_count as comment_count %}
+ (one comment)
+ {% plural %}
+ the answer has been commented {{comment_count}} times
+ {% endblocktrans %}
+ </span>
+ {% endif %}
</div>
</div>
{% endfor %}
</div>
+ <br/>
<a name="votes"></a>
- <h2><span class="count">{{total_votes}}</span> {% trans "Votes" %}</h2>
+ {% spaceless %}
+ <h2>
+ {% blocktrans count total_votes as cnt %}
+ <span class="count">1</span> Vote
+ {% plural %}
+ <span class="count">{{cnt}}</span> Votes
+ {% endblocktrans %}
+ </h2>
+ {% endspaceless %}
<div class="user-stats-table">
<table>
<tr>
<td width="60">
- <img style="cursor: default;" src="/content/images/vote-arrow-up-on.png" alt="{% trans "thumb up" %}" />
+ <img style="cursor: default;" src="{% href "/content/images/vote-arrow-up-on.png" %}" alt="{% trans "thumb up" %}" />
<span title="{% trans "user has voted up this many times" %}" class="vote-count">{{up_votes}}</span>
</td>
<td width="60">
- <img style="cursor: default;" src="/content/images/vote-arrow-down-on.png" alt="{% trans "thumb down" %}" />
+ <img style="cursor: default;" src="{% href "/content/images/vote-arrow-down-on.png" %}" alt="{% trans "thumb down" %}" />
<span title="{% trans "user voted down this many times" %}" class="vote-count">{{down_votes}}</span>
</td>
@@ -47,15 +82,24 @@
</table>
</div>
<a name="tags"></a>
- <h2><span class="count">{{tags|length}}</span> {% trans "Tags" %}</h2>
+ {% spaceless %}
+ <h2>
+ {% blocktrans count user_tags|length as counter %}
+ <span class="count">1</span> Tag
+ {% plural %}
+ <span class="count">{{counter}}</span> Tags
+ {% endblocktrans %}
+ </h2>
+ {% endspaceless %}
<div class="user-stats-table">
<table class="tags">
<tr>
<td width="180" valign="top">
- {% for tag in tags%}
+ {% for tag in user_tags%}
<a rel="tag"
- title="{% blocktrans %}see other questions tagged '{{ tag }}' {% endblocktrans %}"
- href="{% url forum.views.tag tag|urlencode %}">{{tag.name}}</a><span class="tag-number"> × {{ tag.used_count|intcomma }}</span><br/>
+ title="{% blocktrans with tag.name as tag_name %}see other questions with {{view_user}}'s contributions tagged '{{ tag_name }}' {% endblocktrans %}"
+ href="{% url forum.views.tag tag|urlencode %}?user={{view_user.username}}">{{tag.name}}</a>
+ <span class="tag-number">&#215; {{ tag.user_tag_usage_count|intcomma }}</span><br/>
{% if forloop.counter|divisibleby:"10" %}
</td>
<td width="180" valign="top">
@@ -66,7 +110,15 @@
</table>
</div>
<a name="badges"></a>
- <h2><span class="count">{{total_awards}}</span> {% trans "Badges" %}</h2>
+ {% spaceless %}
+ <h2>
+ {% blocktrans count total_awards as counter %}
+ <span class="count">1</span> Badge
+ {% plural %}
+ <span class="count">{{counter}}</span> Badges
+ {% endblocktrans %}
+ </h2>
+ {% endspaceless %}
<div class="user-stats-table">
<table>
<tr>
@@ -82,6 +134,5 @@
</tr>
</table>
</div>
-
{% endblock %}
<!-- end user_stats.html -->
diff --git a/templates/user_tabs.html b/templates/user_tabs.html
index cb7e1d2f..908e8430 100644
--- a/templates/user_tabs.html
+++ b/templates/user_tabs.html
@@ -23,9 +23,9 @@
title="{% trans "questions that user selected as his/her favorite" %}"
href="{% url 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="{% url users %}{{view_user.id}}/{{view_user.username}}?sort=preferences">{% trans "settings" %}</a>
+ <a id="email_subscriptions" {% ifequal tab_name "email_subscriptions" %}class="on"{% endifequal %}
+ title="{% trans "email subscription settings" %}"
+ href="{% url users %}{{view_user.id}}/{{view_user.username}}?sort=email_subscriptions">{% trans "email subscriptions" %}</a>
{% endif %}
</div>
</div>
diff --git a/templates/user_votes.html b/templates/user_votes.html
index afe5827f..94d7fcbd 100644
--- a/templates/user_votes.html
+++ b/templates/user_votes.html
@@ -1,6 +1,7 @@
{% extends "user.html" %}
<!-- user_votes.html -->
{% load extra_tags %}
+{% load extra_filters %}
{% load humanize %}
{% load i18n %}
@@ -11,16 +12,16 @@
<div style="width:150px;float:left">{% diff_date vote.voted_at 3 %}</div>
<div style="width:30px;float:left">
{% ifequal vote.vote 1 %}
- <img src="/content/images/vote-arrow-up-on.png" title="{% trans "upvote" %}">
+ <img src="{% href "/content/images/vote-arrow-up-on.png" %}" title="{% trans "upvote" %}">
{% else %}
- <img src="/content/images/vote-arrow-down-on.png" title="{% trans "downvote" %}">
+ <img src="{% href "/content/images/vote-arrow-down-on.png" %}" title="{% trans "downvote" %}">
{% endifequal %}
</div>
<div style="float:left;overflow:hidden;width:750px">
{% ifequal vote.answer_id 0 %}
- <span class="question-title-link"><a href="{% url questions %}{{ vote.question_id }}/{{ vote.title|slugify }}">{{ vote.title }}</a></span>
+ <span class="question-title-link"><a href="{% url question vote.question_id %}{{ vote.title|slugify }}">{{ vote.title }}</a></span>
{% else %}
- <span class="answer-title-link" ><a href="{% url questions %}{{ vote.question_id }}/{{ vote.title|slugify }}#{{ vote.answer_id }}">{{ vote.title }}</a></span>
+ <span class="answer-title-link" ><a href="{% url question vote.question_id %}{{ vote.title|slugify }}#{{ vote.answer_id }}">{{ vote.title }}</a></span>
{% endifequal %}
<div style="height:5px"></div>
</div>
diff --git a/templates/users.html b/templates/users.html
index 966596fc..3a59b0c0 100644
--- a/templates/users.html
+++ b/templates/users.html
@@ -23,10 +23,10 @@
<div class="tabBar">
<div class="headUsers">{% trans "Users" %}</div>
<div class="tabsA">
- <a id="sort_reputation" href="?sort=reputation" class="off" title="{% trans "reputation" %}">{% trans "reputation" %}</a>
- <a id="sort_newest" href="?sort=newest" class="off" title="{% trans "recent" %}">{% trans "recent" %}</a>
- <a id="sort_last" href="?sort=last" class="off" title="{% trans "oldest" %}">{% trans "oldest" %}</a>
- <a id="sort_user" href="?sort=user" class="off" title="{% trans "by username" %}">{% trans "by username" %}</a>
+ <a id="sort_reputation" href="{% url users %}?sort=reputation" class="off" title="{% trans "reputation" %}">{% trans "reputation" %}</a>
+ <a id="sort_newest" href="{% url users %}?sort=newest" class="off" title="{% trans "recent" %}">{% trans "recent" %}</a>
+ <a id="sort_last" href="{% url users %}?sort=last" class="off" title="{% trans "oldest" %}">{% trans "oldest" %}</a>
+ <a id="sort_user" href="{% url users %}?sort=user" class="off" title="{% trans "by username" %}">{% trans "by username" %}</a>
</div>
</div>
<div id="main-body" style="width:100%">
diff --git a/templates/users_questions.html b/templates/users_questions.html
index 7a00d82d..b445a74c 100644
--- a/templates/users_questions.html
+++ b/templates/users_questions.html
@@ -4,20 +4,20 @@
{% load humanize %}
{% load i18n %}
<div class="user-stats-table">
- {% for question in questions%}
+ {% for question in questions %}
{% if question.favourite_count %}
{% if question.favorited_myself %}
<div class="favorites-count">
<img title="{% trans "this questions was selected as favorite" %} {{question.favourite_count}} {% trans "number of times" %}"
alt="{% trans "thumb-up on" %}"
- src="/content/images/vote-favorite-on.png"/>
+ src="{% href "/content/images/vote-favorite-on.png" %}"/>
<div><b>{{question.favourite_count|intcomma}}</b></div>
</div>
{% else %}
<div class="favorites-count-off">
<img title="{% trans "this question was selected as favorite" %}{{question.favourite_count}} {% trans "number of times" %}"
alt="{% trans "thumb-up off" %}"
- src="/content/images/vote-favorite-off.png"/>
+ src="{% href "/content/images/vote-favorite-off.png" %}"/>
<div><b>{{question.favourite_count|intcomma}}</b></div>
</div>
{% endif %}
@@ -25,28 +25,26 @@
<div class="favorites-empty"> </div>
{% endif %}
<div id="question-summary-{{question.id}}" class="question-summary narrow">
- <a style="text-decoration: none;" href="{{ question.get_absolute_url}}">
- <span class="stats">
- <span class="votes">
- <span class="vote-count-post">{{question.vote_count|intcomma}}</span>
+ <a style="text-decoration: none;" href="{% url question id=question.id %}{{question.title|slugify}}">
+ <div class="stats">
+ <div class="votes">
+ <span class="vote-count-post">{{question.vote_count|intcomma}}</span>
{% trans "votes" %}
-
- </span>
- <span title="{% if question.answer_accepted %}{% trans "this answer has been accepted to be correct" %}{% endif %}" class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
+ </div >
+ <div title="{% if question.answer_accepted %}{% trans "this answer has been accepted to be correct" %}{% endif %}" class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
<span class="answer-count-post">{{question.answer_count|intcomma}}</span>
{% trans "answers" %}
-
- </span>
- <span class="views">
+ </div>
+ <div class="views">
<span class="views-count-post">{{question.view_count|cnprog_intword|safe}}</span>
{% trans "views" %}
- </span>
- </span>
+ </div>
+ </div>
</a>
<div class="summary">
- <h3>
- <a title="{{question.summary}}" href="{% url question question.id%}{{question.title|slugify}}">{{question.title}}</a>
- </h3>
+ <div class="question-title">
+ <a title="{{question.summary}}" href="{% url question id=question.id %}{{question.title|slugify}}">{{question.title}}</a>
+ </div>
<div class="tags">
{% convert2tagname_list question %}
{% for tag in question.tagnames %}
diff --git a/urls.py b/urls.py
index 0001a881..0608aaa8 100644
--- a/urls.py
+++ b/urls.py
@@ -1,70 +1,7 @@
-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
from django.utils.translation import ugettext as _
+import settings
-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'^%s(?P<path>.*)$' % _('upfiles/'), 'django.views.static.serve',
- {'document_root': os.path.join(APP_PATH, 'templates/upfiles').replace('\\','/')}
- ),
- (r'^%s' % _('account/'), include('django_authopenid.urls')),
- (r'^%s/$' % _('signin/'), 'django_authopenid.views.signin'),
- (r'^%s$' % _('signup/'), 'django_authopenid.views.signup'),
- url(r'^%s%s$' % (_('email/'), _('change/')), 'django_authopenid.views.changeemail', name='user_changeemail'),
- url(r'^%s%s$' % (_('email/'), _('sendkey/')), 'django_authopenid.views.send_email_key'),
- url(r'^%s%s(?P<id>\d+)/(?P<key>[\dabcdef]{32})/$' % (_('email/'), _('verify/')), 'django_authopenid.views.verifyemail', name='user_verifyemail'),
- url(r'^%s$' % _('about/'), app.about, name='about'),
- url(r'^%s$' % _('faq/'), app.faq, name='faq'),
- url(r'^%s$' % _('privacy/'), app.privacy, name='privacy'),
- url(r'^%s$' % _('logout/'), app.logout, name='logout'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('comments/')), app.answer_comments, name='answer_comments'),
- url(r'^answers/(?P<id>\d+)/comments/$', app.answer_comments, name='answer_comments'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')), app.edit_answer, name='edit_answer'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')), app.answer_revisions, name='answer_revisions'),
- url(r'^%s$' % _('questions/'), app.questions, name='questions'),
- url(r'^%s%s$' % (_('questions/'), _('ask/')), app.ask, name='ask'),
- url(r'^%s%s$' % (_('questions/'), _('unanswered/')), app.unanswered, name='unanswered'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.edit_question, name='edit_question'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.close, name='close'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.reopen, name='reopen'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.answer, name='answer'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')), app.vote, name='vote'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.question_revisions, name='question_revisions'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('comments/')), app.question_comments, name='question_comments'),
- url(r'^questions/(?P<id>\d+)/comments/$', app.question_comments, name='question_comments'),
- url(r'^%s(?P<question_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('questions/'), _('questions/'),_('delete/')), app.delete_question_comment, name='delete_question_comment'),
- url(r'^%s(?P<answer_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('answers/'), _('answers/'),_('delete/')), app.delete_answer_comment, name='delete_answer_comment'),
- #place general question item in the end of other operations
- url(r'^%s(?P<id>\d+)//*' % _('question/'), app.question, name='question'),
- url(r'^%s$' % _('tags/'), app.tags, name='tags'),
- url(r'^%s(?P<tag>[^/]+)/$' % _('tags/'), app.tag),
- url(r'^%s$' % _('users/'),app.users, name='users'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), app.edit_user, name='edit_user'),
- url(r'^%s(?P<id>\d+)//*' % _('users/'), app.user, name='user'),
- url(r'^%s$' % _('badges/'),app.badges, name='badges'),
- url(r'^%s(?P<id>\d+)//*' % _('badges/'), app.badge, name='badge'),
- url(r'^%s%s$' % (_('messages/'), _('markread/')),app.read_message, name='read_message'),
- # (r'^admin/doc/' % _('admin/doc'), include('django.contrib.admindocs.urls')),
- (r'^%s(.*)' % _('nimda/'), admin.site.root),
- url(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
- (r'^%s$' % _('upload/'), app.upload),
- url(r'^%s$' % _('books/'), app.books, name='books'),
- url(r'^%s%s(?P<short_name>[^/]+)/$' % (_('books/'), _('ask/')), app.ask_book, name='ask_book'),
- url(r'^%s(?P<short_name>[^/]+)/$' % _('books/'), app.book, name='book'),
- url(r'^%s$' % _('search/'), app.search, name='search'),
+ (r'^%s' % settings.FORUM_SCRIPT_ALIAS, include('forum.urls')),
)
diff --git a/user_messages/__init__.py b/user_messages/__init__.py
new file mode 100644
index 00000000..0136c888
--- /dev/null
+++ b/user_messages/__init__.py
@@ -0,0 +1,36 @@
+"""
+Lightweight session-based messaging system.
+
+Time-stamp: <2009-03-10 19:22:29 carljm __init__.py>
+
+"""
+VERSION = (0, 1, 'pre')
+
+def create_message (request, message):
+ """
+ Create a message in the current session.
+
+ """
+ assert hasattr(request, 'session'), "django-session-messages requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
+
+ try:
+ request.session['messages'].append(message)
+ except KeyError:
+ request.session['messages'] = [message]
+
+def get_and_delete_messages (request, include_auth=False):
+ """
+ Get and delete all messages for current session.
+
+ Optionally also fetches user messages from django.contrib.auth.
+
+ """
+ assert hasattr(request, 'session'), "django-session-messages requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
+
+ messages = request.session.pop('messages', [])
+
+ if include_auth and request.user.is_authenticated():
+ messages.extend(request.user.get_and_delete_messages())
+
+ return messages
+
diff --git a/user_messages/context_processors.py b/user_messages/context_processors.py
new file mode 100644
index 00000000..894f5801
--- /dev/null
+++ b/user_messages/context_processors.py
@@ -0,0 +1,52 @@
+"""
+Context processor for lightweight session messages.
+
+Time-stamp: <2008-07-19 23:16:19 carljm context_processors.py>
+
+"""
+from django.utils.encoding import StrAndUnicode
+
+from user_messages import get_and_delete_messages
+
+def user_messages (request):
+ """
+ Returns session messages for the current session.
+
+ """
+ messages = request.user.get_and_delete_messages()
+ #if request.user.is_authenticated():
+ #else:
+ # messages = LazyMessages(request)
+ return { 'user_messages': messages }
+
+class LazyMessages (StrAndUnicode):
+ """
+ Lazy message container, so messages aren't actually retrieved from
+ session and deleted until the template asks for them.
+
+ """
+ def __init__(self, request):
+ self.request = request
+
+ def __iter__(self):
+ return iter(self.messages)
+
+ def __len__(self):
+ return len(self.messages)
+
+ def __nonzero__(self):
+ return bool(self.messages)
+
+ def __unicode__(self):
+ return unicode(self.messages)
+
+ def __getitem__(self, *args, **kwargs):
+ return self.messages.__getitem__(*args, **kwargs)
+
+ def _get_messages(self):
+ if hasattr(self, '_messages'):
+ return self._messages
+ self._messages = get_and_delete_messages(self.request)
+ return self._messages
+ messages = property(_get_messages)
+
diff --git a/user_messages/models.py b/user_messages/models.py
new file mode 100644
index 00000000..b67ead6d
--- /dev/null
+++ b/user_messages/models.py
@@ -0,0 +1,3 @@
+"""
+blank models.py
+"""
diff --git a/utils/decorators.py b/utils/decorators.py
new file mode 100644
index 00000000..e4e7acb3
--- /dev/null
+++ b/utils/decorators.py
@@ -0,0 +1,25 @@
+from django.http import HttpResponse, HttpResponseForbidden, Http404
+from django.utils import simplejson
+
+def ajax_login_required(view_func):
+ def wrap(request,*args,**kwargs):
+ if request.user.is_authenticated():
+ return view_func(request,*args,**kwargs)
+ else:
+ json = simplejson.dumps({'login_required':True})
+ return HttpResponseForbidden(json,mimetype='application/json')
+ return wrap
+
+def ajax_method(view_func):
+ def wrap(request,*args,**kwargs):
+ if not request.is_ajax():
+ raise Http404
+ retval = view_func(request,*args,**kwargs)
+ if isinstance(retval, HttpResponse):
+ retval.mimetype = 'application/json'
+ return retval
+ else:
+ json = simplejson.dumps(retval)
+ return HttpResponse(json,mimetype='application/json')
+ return wrap
+
diff --git a/utils/odict.py b/utils/odict.py
new file mode 100644
index 00000000..2c8391d7
--- /dev/null
+++ b/utils/odict.py
@@ -0,0 +1,1399 @@
+# odict.py
+# An Ordered Dictionary object
+# Copyright (C) 2005 Nicola Larosa, Michael Foord
+# E-mail: nico AT tekNico DOT net, fuzzyman AT voidspace DOT org DOT uk
+
+# This software is licensed under the terms of the BSD license.
+# http://www.voidspace.org.uk/python/license.shtml
+# Basically you're free to copy, modify, distribute and relicense it,
+# So long as you keep a copy of the license with it.
+
+# Documentation at http://www.voidspace.org.uk/python/odict.html
+# For information about bugfixes, updates and support, please join the
+# Pythonutils mailing list:
+# http://groups.google.com/group/pythonutils/
+# Comments, suggestions and bug reports welcome.
+
+"""A dict that keeps keys in insertion order"""
+from __future__ import generators
+
+__author__ = ('Nicola Larosa <nico-NoSp@m-tekNico.net>,'
+ 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>')
+
+__docformat__ = "restructuredtext en"
+
+__revision__ = '$Id: odict.py 129 2005-09-12 18:15:28Z teknico $'
+
+__version__ = '0.2.2'
+
+__all__ = ['OrderedDict', 'SequenceOrderedDict']
+
+import sys
+INTP_VER = sys.version_info[:2]
+if INTP_VER < (2, 2):
+ raise RuntimeError("Python v.2.2 or later required")
+
+import types, warnings
+
+class OrderedDict(dict):
+ """
+ A class of dictionary that keeps the insertion order of keys.
+
+ All appropriate methods return keys, items, or values in an ordered way.
+
+ All normal dictionary methods are available. Update and comparison is
+ restricted to other OrderedDict objects.
+
+ Various sequence methods are available, including the ability to explicitly
+ mutate the key ordering.
+
+ __contains__ tests:
+
+ >>> d = OrderedDict(((1, 3),))
+ >>> 1 in d
+ 1
+ >>> 4 in d
+ 0
+
+ __getitem__ tests:
+
+ >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[2]
+ 1
+ >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[4]
+ Traceback (most recent call last):
+ KeyError: 4
+
+ __len__ tests:
+
+ >>> len(OrderedDict())
+ 0
+ >>> len(OrderedDict(((1, 3), (3, 2), (2, 1))))
+ 3
+
+ get tests:
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.get(1)
+ 3
+ >>> d.get(4) is None
+ 1
+ >>> d.get(4, 5)
+ 5
+ >>> d
+ OrderedDict([(1, 3), (3, 2), (2, 1)])
+
+ has_key tests:
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.has_key(1)
+ 1
+ >>> d.has_key(4)
+ 0
+ """
+
+ def __init__(self, init_val=(), strict=False):
+ """
+ Create a new ordered dictionary. Cannot init from a normal dict,
+ nor from kwargs, since items order is undefined in those cases.
+
+ If the ``strict`` keyword argument is ``True`` (``False`` is the
+ default) then when doing slice assignment - the ``OrderedDict`` you are
+ assigning from *must not* contain any keys in the remaining dict.
+
+ >>> OrderedDict()
+ OrderedDict([])
+ >>> OrderedDict({1: 1})
+ Traceback (most recent call last):
+ TypeError: undefined order, cannot get items from dict
+ >>> OrderedDict({1: 1}.items())
+ OrderedDict([(1, 1)])
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d
+ OrderedDict([(1, 3), (3, 2), (2, 1)])
+ >>> OrderedDict(d)
+ OrderedDict([(1, 3), (3, 2), (2, 1)])
+ """
+ self.strict = strict
+ dict.__init__(self)
+ if isinstance(init_val, OrderedDict):
+ self._sequence = init_val.keys()
+ dict.update(self, init_val)
+ elif isinstance(init_val, dict):
+ # we lose compatibility with other ordered dict types this way
+ raise TypeError('undefined order, cannot get items from dict')
+ else:
+ self._sequence = []
+ self.update(init_val)
+
+### Special methods ###
+
+ def __delitem__(self, key):
+ """
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> del d[3]
+ >>> d
+ OrderedDict([(1, 3), (2, 1)])
+ >>> del d[3]
+ Traceback (most recent call last):
+ KeyError: 3
+ >>> d[3] = 2
+ >>> d
+ OrderedDict([(1, 3), (2, 1), (3, 2)])
+ >>> del d[0:1]
+ >>> d
+ OrderedDict([(2, 1), (3, 2)])
+ """
+ if isinstance(key, types.SliceType):
+ # FIXME: efficiency?
+ keys = self._sequence[key]
+ for entry in keys:
+ dict.__delitem__(self, entry)
+ del self._sequence[key]
+ else:
+ # do the dict.__delitem__ *first* as it raises
+ # the more appropriate error
+ dict.__delitem__(self, key)
+ self._sequence.remove(key)
+
+ def __eq__(self, other):
+ """
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d == OrderedDict(d)
+ True
+ >>> d == OrderedDict(((1, 3), (2, 1), (3, 2)))
+ False
+ >>> d == OrderedDict(((1, 0), (3, 2), (2, 1)))
+ False
+ >>> d == OrderedDict(((0, 3), (3, 2), (2, 1)))
+ False
+ >>> d == dict(d)
+ False
+ >>> d == False
+ False
+ """
+ if isinstance(other, OrderedDict):
+ # FIXME: efficiency?
+ # Generate both item lists for each compare
+ return (self.items() == other.items())
+ else:
+ return False
+
+ def __lt__(self, other):
+ """
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
+ >>> c < d
+ True
+ >>> d < c
+ False
+ >>> d < dict(c)
+ Traceback (most recent call last):
+ TypeError: Can only compare with other OrderedDicts
+ """
+ if not isinstance(other, OrderedDict):
+ raise TypeError('Can only compare with other OrderedDicts')
+ # FIXME: efficiency?
+ # Generate both item lists for each compare
+ return (self.items() < other.items())
+
+ def __le__(self, other):
+ """
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
+ >>> e = OrderedDict(d)
+ >>> c <= d
+ True
+ >>> d <= c
+ False
+ >>> d <= dict(c)
+ Traceback (most recent call last):
+ TypeError: Can only compare with other OrderedDicts
+ >>> d <= e
+ True
+ """
+ if not isinstance(other, OrderedDict):
+ raise TypeError('Can only compare with other OrderedDicts')
+ # FIXME: efficiency?
+ # Generate both item lists for each compare
+ return (self.items() <= other.items())
+
+ def __ne__(self, other):
+ """
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d != OrderedDict(d)
+ False
+ >>> d != OrderedDict(((1, 3), (2, 1), (3, 2)))
+ True
+ >>> d != OrderedDict(((1, 0), (3, 2), (2, 1)))
+ True
+ >>> d == OrderedDict(((0, 3), (3, 2), (2, 1)))
+ False
+ >>> d != dict(d)
+ True
+ >>> d != False
+ True
+ """
+ if isinstance(other, OrderedDict):
+ # FIXME: efficiency?
+ # Generate both item lists for each compare
+ return not (self.items() == other.items())
+ else:
+ return True
+
+ def __gt__(self, other):
+ """
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
+ >>> d > c
+ True
+ >>> c > d
+ False
+ >>> d > dict(c)
+ Traceback (most recent call last):
+ TypeError: Can only compare with other OrderedDicts
+ """
+ if not isinstance(other, OrderedDict):
+ raise TypeError('Can only compare with other OrderedDicts')
+ # FIXME: efficiency?
+ # Generate both item lists for each compare
+ return (self.items() > other.items())
+
+ def __ge__(self, other):
+ """
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
+ >>> e = OrderedDict(d)
+ >>> c >= d
+ False
+ >>> d >= c
+ True
+ >>> d >= dict(c)
+ Traceback (most recent call last):
+ TypeError: Can only compare with other OrderedDicts
+ >>> e >= d
+ True
+ """
+ if not isinstance(other, OrderedDict):
+ raise TypeError('Can only compare with other OrderedDicts')
+ # FIXME: efficiency?
+ # Generate both item lists for each compare
+ return (self.items() >= other.items())
+
+ def __repr__(self):
+ """
+ Used for __repr__ and __str__
+
+ >>> r1 = repr(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
+ >>> r1
+ "OrderedDict([('a', 'b'), ('c', 'd'), ('e', 'f')])"
+ >>> r2 = repr(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd'))))
+ >>> r2
+ "OrderedDict([('a', 'b'), ('e', 'f'), ('c', 'd')])"
+ >>> r1 == str(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
+ True
+ >>> r2 == str(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd'))))
+ True
+ """
+ return '%s([%s])' % (self.__class__.__name__, ', '.join(
+ ['(%r, %r)' % (key, self[key]) for key in self._sequence]))
+
+ def __setitem__(self, key, val):
+ """
+ Allows slice assignment, so long as the slice is an OrderedDict
+ >>> d = OrderedDict()
+ >>> d['a'] = 'b'
+ >>> d['b'] = 'a'
+ >>> d[3] = 12
+ >>> d
+ OrderedDict([('a', 'b'), ('b', 'a'), (3, 12)])
+ >>> d[:] = OrderedDict(((1, 2), (2, 3), (3, 4)))
+ >>> d
+ OrderedDict([(1, 2), (2, 3), (3, 4)])
+ >>> d[::2] = OrderedDict(((7, 8), (9, 10)))
+ >>> d
+ OrderedDict([(7, 8), (2, 3), (9, 10)])
+ >>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4)))
+ >>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8)))
+ >>> d
+ OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
+ >>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4)), strict=True)
+ >>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8)))
+ >>> d
+ OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
+
+ >>> a = OrderedDict(((0, 1), (1, 2), (2, 3)), strict=True)
+ >>> a[3] = 4
+ >>> a
+ OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> a
+ OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)])
+ Traceback (most recent call last):
+ ValueError: slice assignment must be from unique keys
+ >>> a = OrderedDict(((0, 1), (1, 2), (2, 3)))
+ >>> a[3] = 4
+ >>> a
+ OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> a
+ OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> a
+ OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> a[::-1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> a
+ OrderedDict([(3, 4), (2, 3), (1, 2), (0, 1)])
+
+ >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> d[:1] = 3
+ Traceback (most recent call last):
+ TypeError: slice assignment requires an OrderedDict
+
+ >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+ >>> d[:1] = OrderedDict([(9, 8)])
+ >>> d
+ OrderedDict([(9, 8), (1, 2), (2, 3), (3, 4)])
+ """
+ if isinstance(key, types.SliceType):
+ if not isinstance(val, OrderedDict):
+ # FIXME: allow a list of tuples?
+ raise TypeError('slice assignment requires an OrderedDict')
+ keys = self._sequence[key]
+ # NOTE: Could use ``range(*key.indices(len(self._sequence)))``
+ indexes = range(len(self._sequence))[key]
+ if key.step is None:
+ # NOTE: new slice may not be the same size as the one being
+ # overwritten !
+ # NOTE: What is the algorithm for an impossible slice?
+ # e.g. d[5:3]
+ pos = key.start or 0
+ del self[key]
+ newkeys = val.keys()
+ for k in newkeys:
+ if k in self:
+ if self.strict:
+ raise ValueError('slice assignment must be from '
+ 'unique keys')
+ else:
+ # NOTE: This removes duplicate keys *first*
+ # so start position might have changed?
+ del self[k]
+ self._sequence = (self._sequence[:pos] + newkeys +
+ self._sequence[pos:])
+ dict.update(self, val)
+ else:
+ # extended slice - length of new slice must be the same
+ # as the one being replaced
+ if len(keys) != len(val):
+ raise ValueError('attempt to assign sequence of size %s '
+ 'to extended slice of size %s' % (len(val), len(keys)))
+ # FIXME: efficiency?
+ del self[key]
+ item_list = zip(indexes, val.items())
+ # smallest indexes first - higher indexes not guaranteed to
+ # exist
+ item_list.sort()
+ for pos, (newkey, newval) in item_list:
+ if self.strict and newkey in self:
+ raise ValueError('slice assignment must be from unique'
+ ' keys')
+ self.insert(pos, newkey, newval)
+ else:
+ if key not in self:
+ self._sequence.append(key)
+ dict.__setitem__(self, key, val)
+
+ def __getitem__(self, key):
+ """
+ Allows slicing. Returns an OrderedDict if you slice.
+ >>> b = OrderedDict([(7, 0), (6, 1), (5, 2), (4, 3), (3, 4), (2, 5), (1, 6)])
+ >>> b[::-1]
+ OrderedDict([(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1), (7, 0)])
+ >>> b[2:5]
+ OrderedDict([(5, 2), (4, 3), (3, 4)])
+ >>> type(b[2:4])
+ <class '__main__.OrderedDict'>
+ """
+ if isinstance(key, types.SliceType):
+ # FIXME: does this raise the error we want?
+ keys = self._sequence[key]
+ # FIXME: efficiency?
+ return OrderedDict([(entry, self[entry]) for entry in keys])
+ else:
+ return dict.__getitem__(self, key)
+
+ __str__ = __repr__
+
+ def __setattr__(self, name, value):
+ """
+ Implemented so that accesses to ``sequence`` raise a warning and are
+ diverted to the new ``setkeys`` method.
+ """
+ if name == 'sequence':
+ warnings.warn('Use of the sequence attribute is deprecated.'
+ ' Use the keys method instead.', DeprecationWarning)
+ # NOTE: doesn't return anything
+ self.setkeys(value)
+ else:
+ # FIXME: do we want to allow arbitrary setting of attributes?
+ # Or do we want to manage it?
+ object.__setattr__(self, name, value)
+
+ def __getattr__(self, name):
+ """
+ Implemented so that access to ``sequence`` raises a warning.
+
+ >>> d = OrderedDict()
+ >>> d.sequence
+ []
+ """
+ if name == 'sequence':
+ warnings.warn('Use of the sequence attribute is deprecated.'
+ ' Use the keys method instead.', DeprecationWarning)
+ # NOTE: Still (currently) returns a direct reference. Need to
+ # because code that uses sequence will expect to be able to
+ # mutate it in place.
+ return self._sequence
+ else:
+ # raise the appropriate error
+ raise AttributeError("OrderedDict has no '%s' attribute" % name)
+
+ def __deepcopy__(self, memo):
+ """
+ To allow deepcopy to work with OrderedDict.
+
+ >>> from copy import deepcopy
+ >>> a = OrderedDict([(1, 1), (2, 2), (3, 3)])
+ >>> a['test'] = {}
+ >>> b = deepcopy(a)
+ >>> b == a
+ True
+ >>> b is a
+ False
+ >>> a['test'] is b['test']
+ False
+ """
+ from copy import deepcopy
+ return self.__class__(deepcopy(self.items(), memo), self.strict)
+
+
+### Read-only methods ###
+
+ def copy(self):
+ """
+ >>> OrderedDict(((1, 3), (3, 2), (2, 1))).copy()
+ OrderedDict([(1, 3), (3, 2), (2, 1)])
+ """
+ return OrderedDict(self)
+
+ def items(self):
+ """
+ ``items`` returns a list of tuples representing all the
+ ``(key, value)`` pairs in the dictionary.
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.items()
+ [(1, 3), (3, 2), (2, 1)]
+ >>> d.clear()
+ >>> d.items()
+ []
+ """
+ return zip(self._sequence, self.values())
+
+ def keys(self):
+ """
+ Return a list of keys in the ``OrderedDict``.
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.keys()
+ [1, 3, 2]
+ """
+ return self._sequence[:]
+
+ def values(self, values=None):
+ """
+ Return a list of all the values in the OrderedDict.
+
+ Optionally you can pass in a list of values, which will replace the
+ current list. The value list must be the same len as the OrderedDict.
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.values()
+ [3, 2, 1]
+ """
+ return [self[key] for key in self._sequence]
+
+ def iteritems(self):
+ """
+ >>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iteritems()
+ >>> ii.next()
+ (1, 3)
+ >>> ii.next()
+ (3, 2)
+ >>> ii.next()
+ (2, 1)
+ >>> ii.next()
+ Traceback (most recent call last):
+ StopIteration
+ """
+ def make_iter(self=self):
+ keys = self.iterkeys()
+ while True:
+ key = keys.next()
+ yield (key, self[key])
+ return make_iter()
+
+ def iterkeys(self):
+ """
+ >>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iterkeys()
+ >>> ii.next()
+ 1
+ >>> ii.next()
+ 3
+ >>> ii.next()
+ 2
+ >>> ii.next()
+ Traceback (most recent call last):
+ StopIteration
+ """
+ return iter(self._sequence)
+
+ __iter__ = iterkeys
+
+ def itervalues(self):
+ """
+ >>> iv = OrderedDict(((1, 3), (3, 2), (2, 1))).itervalues()
+ >>> iv.next()
+ 3
+ >>> iv.next()
+ 2
+ >>> iv.next()
+ 1
+ >>> iv.next()
+ Traceback (most recent call last):
+ StopIteration
+ """
+ def make_iter(self=self):
+ keys = self.iterkeys()
+ while True:
+ yield self[keys.next()]
+ return make_iter()
+
+### Read-write methods ###
+
+ def clear(self):
+ """
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.clear()
+ >>> d
+ OrderedDict([])
+ """
+ dict.clear(self)
+ self._sequence = []
+
+ def pop(self, key, *args):
+ """
+ No dict.pop in Python 2.2, gotta reimplement it
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.pop(3)
+ 2
+ >>> d
+ OrderedDict([(1, 3), (2, 1)])
+ >>> d.pop(4)
+ Traceback (most recent call last):
+ KeyError: 4
+ >>> d.pop(4, 0)
+ 0
+ >>> d.pop(4, 0, 1)
+ Traceback (most recent call last):
+ TypeError: pop expected at most 2 arguments, got 3
+ """
+ if len(args) > 1:
+ raise TypeError, ('pop expected at most 2 arguments, got %s' %
+ (len(args) + 1))
+ if key in self:
+ val = self[key]
+ del self[key]
+ else:
+ try:
+ val = args[0]
+ except IndexError:
+ raise KeyError(key)
+ return val
+
+ def popitem(self, i=-1):
+ """
+ Delete and return an item specified by index, not a random one as in
+ dict. The index is -1 by default (the last item).
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.popitem()
+ (2, 1)
+ >>> d
+ OrderedDict([(1, 3), (3, 2)])
+ >>> d.popitem(0)
+ (1, 3)
+ >>> OrderedDict().popitem()
+ Traceback (most recent call last):
+ KeyError: 'popitem(): dictionary is empty'
+ >>> d.popitem(2)
+ Traceback (most recent call last):
+ IndexError: popitem(): index 2 not valid
+ """
+ if not self._sequence:
+ raise KeyError('popitem(): dictionary is empty')
+ try:
+ key = self._sequence[i]
+ except IndexError:
+ raise IndexError('popitem(): index %s not valid' % i)
+ return (key, self.pop(key))
+
+ def setdefault(self, key, defval = None):
+ """
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.setdefault(1)
+ 3
+ >>> d.setdefault(4) is None
+ True
+ >>> d
+ OrderedDict([(1, 3), (3, 2), (2, 1), (4, None)])
+ >>> d.setdefault(5, 0)
+ 0
+ >>> d
+ OrderedDict([(1, 3), (3, 2), (2, 1), (4, None), (5, 0)])
+ """
+ if key in self:
+ return self[key]
+ else:
+ self[key] = defval
+ return defval
+
+ def update(self, from_od):
+ """
+ Update from another OrderedDict or sequence of (key, value) pairs
+
+ >>> d = OrderedDict(((1, 0), (0, 1)))
+ >>> d.update(OrderedDict(((1, 3), (3, 2), (2, 1))))
+ >>> d
+ OrderedDict([(1, 3), (0, 1), (3, 2), (2, 1)])
+ >>> d.update({4: 4})
+ Traceback (most recent call last):
+ TypeError: undefined order, cannot get items from dict
+ >>> d.update((4, 4))
+ Traceback (most recent call last):
+ TypeError: cannot convert dictionary update sequence element "4" to a 2-item sequence
+ """
+ if isinstance(from_od, OrderedDict):
+ for key, val in from_od.items():
+ self[key] = val
+ elif isinstance(from_od, dict):
+ # we lose compatibility with other ordered dict types this way
+ raise TypeError('undefined order, cannot get items from dict')
+ else:
+ # FIXME: efficiency?
+ # sequence of 2-item sequences, or error
+ for item in from_od:
+ try:
+ key, val = item
+ except TypeError:
+ raise TypeError('cannot convert dictionary update'
+ ' sequence element "%s" to a 2-item sequence' % item)
+ self[key] = val
+
+ def rename(self, old_key, new_key):
+ """
+ Rename the key for a given value, without modifying sequence order.
+
+ For the case where new_key already exists this raise an exception,
+ since if new_key exists, it is ambiguous as to what happens to the
+ associated values, and the position of new_key in the sequence.
+
+ >>> od = OrderedDict()
+ >>> od['a'] = 1
+ >>> od['b'] = 2
+ >>> od.items()
+ [('a', 1), ('b', 2)]
+ >>> od.rename('b', 'c')
+ >>> od.items()
+ [('a', 1), ('c', 2)]
+ >>> od.rename('c', 'a')
+ Traceback (most recent call last):
+ ValueError: New key already exists: 'a'
+ >>> od.rename('d', 'b')
+ Traceback (most recent call last):
+ KeyError: 'd'
+ """
+ if new_key == old_key:
+ # no-op
+ return
+ if new_key in self:
+ raise ValueError("New key already exists: %r" % new_key)
+ # rename sequence entry
+ value = self[old_key]
+ old_idx = self._sequence.index(old_key)
+ self._sequence[old_idx] = new_key
+ # rename internal dict entry
+ dict.__delitem__(self, old_key)
+ dict.__setitem__(self, new_key, value)
+
+ def setitems(self, items):
+ """
+ This method allows you to set the items in the dict.
+
+ It takes a list of tuples - of the same sort returned by the ``items``
+ method.
+
+ >>> d = OrderedDict()
+ >>> d.setitems(((3, 1), (2, 3), (1, 2)))
+ >>> d
+ OrderedDict([(3, 1), (2, 3), (1, 2)])
+ """
+ self.clear()
+ # FIXME: this allows you to pass in an OrderedDict as well :-)
+ self.update(items)
+
+ def setkeys(self, keys):
+ """
+ ``setkeys`` all ows you to pass in a new list of keys which will
+ replace the current set. This must contain the same set of keys, but
+ need not be in the same order.
+
+ If you pass in new keys that don't match, a ``KeyError`` will be
+ raised.
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.keys()
+ [1, 3, 2]
+ >>> d.setkeys((1, 2, 3))
+ >>> d
+ OrderedDict([(1, 3), (2, 1), (3, 2)])
+ >>> d.setkeys(['a', 'b', 'c'])
+ Traceback (most recent call last):
+ KeyError: 'Keylist is not the same as current keylist.'
+ """
+ # FIXME: Efficiency? (use set for Python 2.4 :-)
+ # NOTE: list(keys) rather than keys[:] because keys[:] returns
+ # a tuple, if keys is a tuple.
+ kcopy = list(keys)
+ kcopy.sort()
+ self._sequence.sort()
+ if kcopy != self._sequence:
+ raise KeyError('Keylist is not the same as current keylist.')
+ # NOTE: This makes the _sequence attribute a new object, instead
+ # of changing it in place.
+ # FIXME: efficiency?
+ self._sequence = list(keys)
+
+ def setvalues(self, values):
+ """
+ You can pass in a list of values, which will replace the
+ current list. The value list must be the same len as the OrderedDict.
+
+ (Or a ``ValueError`` is raised.)
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.setvalues((1, 2, 3))
+ >>> d
+ OrderedDict([(1, 1), (3, 2), (2, 3)])
+ >>> d.setvalues([6])
+ Traceback (most recent call last):
+ ValueError: Value list is not the same length as the OrderedDict.
+ """
+ if len(values) != len(self):
+ # FIXME: correct error to raise?
+ raise ValueError('Value list is not the same length as the '
+ 'OrderedDict.')
+ self.update(zip(self, values))
+
+### Sequence Methods ###
+
+ def index(self, key):
+ """
+ Return the position of the specified key in the OrderedDict.
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.index(3)
+ 1
+ >>> d.index(4)
+ Traceback (most recent call last):
+ ValueError: list.index(x): x not in list
+ """
+ return self._sequence.index(key)
+
+ def insert(self, index, key, value):
+ """
+ Takes ``index``, ``key``, and ``value`` as arguments.
+
+ Sets ``key`` to ``value``, so that ``key`` is at position ``index`` in
+ the OrderedDict.
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.insert(0, 4, 0)
+ >>> d
+ OrderedDict([(4, 0), (1, 3), (3, 2), (2, 1)])
+ >>> d.insert(0, 2, 1)
+ >>> d
+ OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2)])
+ >>> d.insert(8, 8, 1)
+ >>> d
+ OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2), (8, 1)])
+ """
+ if key in self:
+ # FIXME: efficiency?
+ del self[key]
+ self._sequence.insert(index, key)
+ dict.__setitem__(self, key, value)
+
+ def reverse(self):
+ """
+ Reverse the order of the OrderedDict.
+
+ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+ >>> d.reverse()
+ >>> d
+ OrderedDict([(2, 1), (3, 2), (1, 3)])
+ """
+ self._sequence.reverse()
+
+ def sort(self, *args, **kwargs):
+ """
+ Sort the key order in the OrderedDict.
+
+ This method takes the same arguments as the ``list.sort`` method on
+ your version of Python.
+
+ >>> d = OrderedDict(((4, 1), (2, 2), (3, 3), (1, 4)))
+ >>> d.sort()
+ >>> d
+ OrderedDict([(1, 4), (2, 2), (3, 3), (4, 1)])
+ """
+ self._sequence.sort(*args, **kwargs)
+
+class Keys(object):
+ # FIXME: should this object be a subclass of list?
+ """
+ Custom object for accessing the keys of an OrderedDict.
+
+ Can be called like the normal ``OrderedDict.keys`` method, but also
+ supports indexing and sequence methods.
+ """
+
+ def __init__(self, main):
+ self._main = main
+
+ def __call__(self):
+ """Pretend to be the keys method."""
+ return self._main._keys()
+
+ def __getitem__(self, index):
+ """Fetch the key at position i."""
+ # NOTE: this automatically supports slicing :-)
+ return self._main._sequence[index]
+
+ def __setitem__(self, index, name):
+ """
+ You cannot assign to keys, but you can do slice assignment to re-order
+ them.
+
+ You can only do slice assignment if the new set of keys is a reordering
+ of the original set.
+ """
+ if isinstance(index, types.SliceType):
+ # FIXME: efficiency?
+ # check length is the same
+ indexes = range(len(self._main._sequence))[index]
+ if len(indexes) != len(name):
+ raise ValueError('attempt to assign sequence of size %s '
+ 'to slice of size %s' % (len(name), len(indexes)))
+ # check they are the same keys
+ # FIXME: Use set
+ old_keys = self._main._sequence[index]
+ new_keys = list(name)
+ old_keys.sort()
+ new_keys.sort()
+ if old_keys != new_keys:
+ raise KeyError('Keylist is not the same as current keylist.')
+ orig_vals = [self._main[k] for k in name]
+ del self._main[index]
+ vals = zip(indexes, name, orig_vals)
+ vals.sort()
+ for i, k, v in vals:
+ if self._main.strict and k in self._main:
+ raise ValueError('slice assignment must be from '
+ 'unique keys')
+ self._main.insert(i, k, v)
+ else:
+ raise ValueError('Cannot assign to keys')
+
+ ### following methods pinched from UserList and adapted ###
+ def __repr__(self): return repr(self._main._sequence)
+
+ # FIXME: do we need to check if we are comparing with another ``Keys``
+ # object? (like the __cast method of UserList)
+ def __lt__(self, other): return self._main._sequence < other
+ def __le__(self, other): return self._main._sequence <= other
+ def __eq__(self, other): return self._main._sequence == other
+ def __ne__(self, other): return self._main._sequence != other
+ def __gt__(self, other): return self._main._sequence > other
+ def __ge__(self, other): return self._main._sequence >= other
+ # FIXME: do we need __cmp__ as well as rich comparisons?
+ def __cmp__(self, other): return cmp(self._main._sequence, other)
+
+ def __contains__(self, item): return item in self._main._sequence
+ def __len__(self): return len(self._main._sequence)
+ def __iter__(self): return self._main.iterkeys()
+ def count(self, item): return self._main._sequence.count(item)
+ def index(self, item, *args): return self._main._sequence.index(item, *args)
+ def reverse(self): self._main._sequence.reverse()
+ def sort(self, *args, **kwds): self._main._sequence.sort(*args, **kwds)
+ def __mul__(self, n): return self._main._sequence*n
+ __rmul__ = __mul__
+ def __add__(self, other): return self._main._sequence + other
+ def __radd__(self, other): return other + self._main._sequence
+
+ ## following methods not implemented for keys ##
+ def __delitem__(self, i): raise TypeError('Can\'t delete items from keys')
+ def __iadd__(self, other): raise TypeError('Can\'t add in place to keys')
+ def __imul__(self, n): raise TypeError('Can\'t multiply keys in place')
+ def append(self, item): raise TypeError('Can\'t append items to keys')
+ def insert(self, i, item): raise TypeError('Can\'t insert items into keys')
+ def pop(self, i=-1): raise TypeError('Can\'t pop items from keys')
+ def remove(self, item): raise TypeError('Can\'t remove items from keys')
+ def extend(self, other): raise TypeError('Can\'t extend keys')
+
+class Items(object):
+ """
+ Custom object for accessing the items of an OrderedDict.
+
+ Can be called like the normal ``OrderedDict.items`` method, but also
+ supports indexing and sequence methods.
+ """
+
+ def __init__(self, main):
+ self._main = main
+
+ def __call__(self):
+ """Pretend to be the items method."""
+ return self._main._items()
+
+ def __getitem__(self, index):
+ """Fetch the item at position i."""
+ if isinstance(index, types.SliceType):
+ # fetching a slice returns an OrderedDict
+ return self._main[index].items()
+ key = self._main._sequence[index]
+ return (key, self._main[key])
+
+ def __setitem__(self, index, item):
+ """Set item at position i to item."""
+ if isinstance(index, types.SliceType):
+ # NOTE: item must be an iterable (list of tuples)
+ self._main[index] = OrderedDict(item)
+ else:
+ # FIXME: Does this raise a sensible error?
+ orig = self._main.keys[index]
+ key, value = item
+ if self._main.strict and key in self and (key != orig):
+ raise ValueError('slice assignment must be from '
+ 'unique keys')
+ # delete the current one
+ del self._main[self._main._sequence[index]]
+ self._main.insert(index, key, value)
+
+ def __delitem__(self, i):
+ """Delete the item at position i."""
+ key = self._main._sequence[i]
+ if isinstance(i, types.SliceType):
+ for k in key:
+ # FIXME: efficiency?
+ del self._main[k]
+ else:
+ del self._main[key]
+
+ ### following methods pinched from UserList and adapted ###
+ def __repr__(self): return repr(self._main.items())
+
+ # FIXME: do we need to check if we are comparing with another ``Items``
+ # object? (like the __cast method of UserList)
+ def __lt__(self, other): return self._main.items() < other
+ def __le__(self, other): return self._main.items() <= other
+ def __eq__(self, other): return self._main.items() == other
+ def __ne__(self, other): return self._main.items() != other
+ def __gt__(self, other): return self._main.items() > other
+ def __ge__(self, other): return self._main.items() >= other
+ def __cmp__(self, other): return cmp(self._main.items(), other)
+
+ def __contains__(self, item): return item in self._main.items()
+ def __len__(self): return len(self._main._sequence) # easier :-)
+ def __iter__(self): return self._main.iteritems()
+ def count(self, item): return self._main.items().count(item)
+ def index(self, item, *args): return self._main.items().index(item, *args)
+ def reverse(self): self._main.reverse()
+ def sort(self, *args, **kwds): self._main.sort(*args, **kwds)
+ def __mul__(self, n): return self._main.items()*n
+ __rmul__ = __mul__
+ def __add__(self, other): return self._main.items() + other
+ def __radd__(self, other): return other + self._main.items()
+
+ def append(self, item):
+ """Add an item to the end."""
+ # FIXME: this is only append if the key isn't already present
+ key, value = item
+ self._main[key] = value
+
+ def insert(self, i, item):
+ key, value = item
+ self._main.insert(i, key, value)
+
+ def pop(self, i=-1):
+ key = self._main._sequence[i]
+ return (key, self._main.pop(key))
+
+ def remove(self, item):
+ key, value = item
+ try:
+ assert value == self._main[key]
+ except (KeyError, AssertionError):
+ raise ValueError('ValueError: list.remove(x): x not in list')
+ else:
+ del self._main[key]
+
+ def extend(self, other):
+ # FIXME: is only a true extend if none of the keys already present
+ for item in other:
+ key, value = item
+ self._main[key] = value
+
+ def __iadd__(self, other):
+ self.extend(other)
+
+ ## following methods not implemented for items ##
+
+ def __imul__(self, n): raise TypeError('Can\'t multiply items in place')
+
+class Values(object):
+ """
+ Custom object for accessing the values of an OrderedDict.
+
+ Can be called like the normal ``OrderedDict.values`` method, but also
+ supports indexing and sequence methods.
+ """
+
+ def __init__(self, main):
+ self._main = main
+
+ def __call__(self):
+ """Pretend to be the values method."""
+ return self._main._values()
+
+ def __getitem__(self, index):
+ """Fetch the value at position i."""
+ if isinstance(index, types.SliceType):
+ return [self._main[key] for key in self._main._sequence[index]]
+ else:
+ return self._main[self._main._sequence[index]]
+
+ def __setitem__(self, index, value):
+ """
+ Set the value at position i to value.
+
+ You can only do slice assignment to values if you supply a sequence of
+ equal length to the slice you are replacing.
+ """
+ if isinstance(index, types.SliceType):
+ keys = self._main._sequence[index]
+ if len(keys) != len(value):
+ raise ValueError('attempt to assign sequence of size %s '
+ 'to slice of size %s' % (len(name), len(keys)))
+ # FIXME: efficiency? Would be better to calculate the indexes
+ # directly from the slice object
+ # NOTE: the new keys can collide with existing keys (or even
+ # contain duplicates) - these will overwrite
+ for key, val in zip(keys, value):
+ self._main[key] = val
+ else:
+ self._main[self._main._sequence[index]] = value
+
+ ### following methods pinched from UserList and adapted ###
+ def __repr__(self): return repr(self._main.values())
+
+ # FIXME: do we need to check if we are comparing with another ``Values``
+ # object? (like the __cast method of UserList)
+ def __lt__(self, other): return self._main.values() < other
+ def __le__(self, other): return self._main.values() <= other
+ def __eq__(self, other): return self._main.values() == other
+ def __ne__(self, other): return self._main.values() != other
+ def __gt__(self, other): return self._main.values() > other
+ def __ge__(self, other): return self._main.values() >= other
+ def __cmp__(self, other): return cmp(self._main.values(), other)
+
+ def __contains__(self, item): return item in self._main.values()
+ def __len__(self): return len(self._main._sequence) # easier :-)
+ def __iter__(self): return self._main.itervalues()
+ def count(self, item): return self._main.values().count(item)
+ def index(self, item, *args): return self._main.values().index(item, *args)
+
+ def reverse(self):
+ """Reverse the values"""
+ vals = self._main.values()
+ vals.reverse()
+ # FIXME: efficiency
+ self[:] = vals
+
+ def sort(self, *args, **kwds):
+ """Sort the values."""
+ vals = self._main.values()
+ vals.sort(*args, **kwds)
+ self[:] = vals
+
+ def __mul__(self, n): return self._main.values()*n
+ __rmul__ = __mul__
+ def __add__(self, other): return self._main.values() + other
+ def __radd__(self, other): return other + self._main.values()
+
+ ## following methods not implemented for values ##
+ def __delitem__(self, i): raise TypeError('Can\'t delete items from values')
+ def __iadd__(self, other): raise TypeError('Can\'t add in place to values')
+ def __imul__(self, n): raise TypeError('Can\'t multiply values in place')
+ def append(self, item): raise TypeError('Can\'t append items to values')
+ def insert(self, i, item): raise TypeError('Can\'t insert items into values')
+ def pop(self, i=-1): raise TypeError('Can\'t pop items from values')
+ def remove(self, item): raise TypeError('Can\'t remove items from values')
+ def extend(self, other): raise TypeError('Can\'t extend values')
+
+class SequenceOrderedDict(OrderedDict):
+ """
+ Experimental version of OrderedDict that has a custom object for ``keys``,
+ ``values``, and ``items``.
+
+ These are callable sequence objects that work as methods, or can be
+ manipulated directly as sequences.
+
+ Test for ``keys``, ``items`` and ``values``.
+
+ >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
+ >>> d
+ SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+ >>> d.keys
+ [1, 2, 3]
+ >>> d.keys()
+ [1, 2, 3]
+ >>> d.setkeys((3, 2, 1))
+ >>> d
+ SequenceOrderedDict([(3, 4), (2, 3), (1, 2)])
+ >>> d.setkeys((1, 2, 3))
+ >>> d.keys[0]
+ 1
+ >>> d.keys[:]
+ [1, 2, 3]
+ >>> d.keys[-1]
+ 3
+ >>> d.keys[-2]
+ 2
+ >>> d.keys[0:2] = [2, 1]
+ >>> d
+ SequenceOrderedDict([(2, 3), (1, 2), (3, 4)])
+ >>> d.keys.reverse()
+ >>> d.keys
+ [3, 1, 2]
+ >>> d.keys = [1, 2, 3]
+ >>> d
+ SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+ >>> d.keys = [3, 1, 2]
+ >>> d
+ SequenceOrderedDict([(3, 4), (1, 2), (2, 3)])
+ >>> a = SequenceOrderedDict()
+ >>> b = SequenceOrderedDict()
+ >>> a.keys == b.keys
+ 1
+ >>> a['a'] = 3
+ >>> a.keys == b.keys
+ 0
+ >>> b['a'] = 3
+ >>> a.keys == b.keys
+ 1
+ >>> b['b'] = 3
+ >>> a.keys == b.keys
+ 0
+ >>> a.keys > b.keys
+ 0
+ >>> a.keys < b.keys
+ 1
+ >>> 'a' in a.keys
+ 1
+ >>> len(b.keys)
+ 2
+ >>> 'c' in d.keys
+ 0
+ >>> 1 in d.keys
+ 1
+ >>> [v for v in d.keys]
+ [3, 1, 2]
+ >>> d.keys.sort()
+ >>> d.keys
+ [1, 2, 3]
+ >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)), strict=True)
+ >>> d.keys[::-1] = [1, 2, 3]
+ >>> d
+ SequenceOrderedDict([(3, 4), (2, 3), (1, 2)])
+ >>> d.keys[:2]
+ [3, 2]
+ >>> d.keys[:2] = [1, 3]
+ Traceback (most recent call last):
+ KeyError: 'Keylist is not the same as current keylist.'
+
+ >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
+ >>> d
+ SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+ >>> d.values
+ [2, 3, 4]
+ >>> d.values()
+ [2, 3, 4]
+ >>> d.setvalues((4, 3, 2))
+ >>> d
+ SequenceOrderedDict([(1, 4), (2, 3), (3, 2)])
+ >>> d.values[::-1]
+ [2, 3, 4]
+ >>> d.values[0]
+ 4
+ >>> d.values[-2]
+ 3
+ >>> del d.values[0]
+ Traceback (most recent call last):
+ TypeError: Can't delete items from values
+ >>> d.values[::2] = [2, 4]
+ >>> d
+ SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+ >>> 7 in d.values
+ 0
+ >>> len(d.values)
+ 3
+ >>> [val for val in d.values]
+ [2, 3, 4]
+ >>> d.values[-1] = 2
+ >>> d.values.count(2)
+ 2
+ >>> d.values.index(2)
+ 0
+ >>> d.values[-1] = 7
+ >>> d.values
+ [2, 3, 7]
+ >>> d.values.reverse()
+ >>> d.values
+ [7, 3, 2]
+ >>> d.values.sort()
+ >>> d.values
+ [2, 3, 7]
+ >>> d.values.append('anything')
+ Traceback (most recent call last):
+ TypeError: Can't append items to values
+ >>> d.values = (1, 2, 3)
+ >>> d
+ SequenceOrderedDict([(1, 1), (2, 2), (3, 3)])
+
+ >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
+ >>> d
+ SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+ >>> d.items()
+ [(1, 2), (2, 3), (3, 4)]
+ >>> d.setitems([(3, 4), (2 ,3), (1, 2)])
+ >>> d
+ SequenceOrderedDict([(3, 4), (2, 3), (1, 2)])
+ >>> d.items[0]
+ (3, 4)
+ >>> d.items[:-1]
+ [(3, 4), (2, 3)]
+ >>> d.items[1] = (6, 3)
+ >>> d.items
+ [(3, 4), (6, 3), (1, 2)]
+ >>> d.items[1:2] = [(9, 9)]
+ >>> d
+ SequenceOrderedDict([(3, 4), (9, 9), (1, 2)])
+ >>> del d.items[1:2]
+ >>> d
+ SequenceOrderedDict([(3, 4), (1, 2)])
+ >>> (3, 4) in d.items
+ 1
+ >>> (4, 3) in d.items
+ 0
+ >>> len(d.items)
+ 2
+ >>> [v for v in d.items]
+ [(3, 4), (1, 2)]
+ >>> d.items.count((3, 4))
+ 1
+ >>> d.items.index((1, 2))
+ 1
+ >>> d.items.index((2, 1))
+ Traceback (most recent call last):
+ ValueError: list.index(x): x not in list
+ >>> d.items.reverse()
+ >>> d.items
+ [(1, 2), (3, 4)]
+ >>> d.items.reverse()
+ >>> d.items.sort()
+ >>> d.items
+ [(1, 2), (3, 4)]
+ >>> d.items.append((5, 6))
+ >>> d.items
+ [(1, 2), (3, 4), (5, 6)]
+ >>> d.items.insert(0, (0, 0))
+ >>> d.items
+ [(0, 0), (1, 2), (3, 4), (5, 6)]
+ >>> d.items.insert(-1, (7, 8))
+ >>> d.items
+ [(0, 0), (1, 2), (3, 4), (7, 8), (5, 6)]
+ >>> d.items.pop()
+ (5, 6)
+ >>> d.items
+ [(0, 0), (1, 2), (3, 4), (7, 8)]
+ >>> d.items.remove((1, 2))
+ >>> d.items
+ [(0, 0), (3, 4), (7, 8)]
+ >>> d.items.extend([(1, 2), (5, 6)])
+ >>> d.items
+ [(0, 0), (3, 4), (7, 8), (1, 2), (5, 6)]
+ """
+
+ def __init__(self, init_val=(), strict=True):
+ OrderedDict.__init__(self, init_val, strict=strict)
+ self._keys = self.keys
+ self._values = self.values
+ self._items = self.items
+ self.keys = Keys(self)
+ self.values = Values(self)
+ self.items = Items(self)
+ self._att_dict = {
+ 'keys': self.setkeys,
+ 'items': self.setitems,
+ 'values': self.setvalues,
+ }
+
+ def __setattr__(self, name, value):
+ """Protect keys, items, and values."""
+ if not '_att_dict' in self.__dict__:
+ object.__setattr__(self, name, value)
+ else:
+ try:
+ fun = self._att_dict[name]
+ except KeyError:
+ OrderedDict.__setattr__(self, name, value)
+ else:
+ fun(value)
+
+if __name__ == '__main__':
+ if INTP_VER < (2, 3):
+ raise RuntimeError("Tests require Python v.2.3 or later")
+ # turn off warnings for tests
+ warnings.filterwarnings('ignore')
+ # run the code tests in doctest format
+ import doctest
+ m = sys.modules.get('__main__')
+ globs = m.__dict__.copy()
+ globs.update({
+ 'INTP_VER': INTP_VER,
+ })
+ doctest.testmod(m, globs=globs)
+