From 026541b6a70cd183d49ffec205232cfb0b205b25 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 5 Aug 2009 22:49:44 -0400 Subject: added anonymous posting, per-question subscription and fixes by Pothers and some more, see development.log --- context.py | 35 +- development.log | 35 +- django_authopenid/forms.py | 82 +- django_authopenid/middleware.py | 2 +- django_authopenid/urls.py | 5 +- django_authopenid/views.py | 208 ++- forum/admin.py | 5 +- forum/feed.py | 14 +- forum/forms.py | 42 +- forum/management/commands/once_award_badges.py | 1 + forum/models.py | 159 ++- forum/templatetags/extra_tags.py | 4 +- forum/user.py | 1 - forum/views.py | 261 +++- locale/en/LC_MESSAGES/django.mo | Bin 3214 -> 0 bytes locale/en/LC_MESSAGES/django.po | 1775 ++++++++++++------------ locale/es/LC_MESSAGES/django.mo | Bin 44804 -> 0 bytes locale/es/LC_MESSAGES/django.po | 1329 +++++++++++------- locale/zh_CN/LC_MESSAGES/django.mo | Bin 36796 -> 0 bytes locale/zh_CN/LC_MESSAGES/django.po | 33 +- settings.py | 15 +- templates/404.html | 2 +- templates/500.html | 4 +- templates/answer_edit.html | 2 +- templates/answer_edit_tips.html | 2 +- templates/ask.html | 52 +- templates/authopenid/changeemail.html | 100 +- templates/authopenid/changeopenid.html | 2 + templates/authopenid/changepw.html | 2 + templates/authopenid/complete.html | 17 +- templates/authopenid/confirm_email.txt | 17 +- templates/authopenid/delete.html | 2 + templates/authopenid/failure.html | 3 +- templates/authopenid/sendpw.html | 2 + templates/authopenid/sendpw_email.txt | 18 +- templates/authopenid/settings.html | 2 + templates/authopenid/signin.html | 124 +- templates/authopenid/signup.html | 2 + templates/badge.html | 2 +- templates/badges.html | 15 +- templates/base.html | 29 +- templates/base_content.html | 27 +- templates/book.html | 2 +- templates/close.html | 2 +- templates/content/js/com.cnprog.i18n.js | 8 +- templates/content/js/com.cnprog.post.js | 23 +- templates/content/js/com.cnprog.utils.js | 2 +- templates/content/style/openid.css | 2 +- templates/content/style/style.css | 212 ++- templates/faq.html | 223 +-- templates/footer.html | 2 +- templates/header.html | 37 +- templates/index.html | 10 +- templates/logout.html | 4 +- templates/pagesize.html | 2 + templates/question.html | 21 +- templates/question_edit.html | 2 +- templates/question_retag.html | 25 +- templates/questions.html | 28 +- templates/revisions_answer.html | 2 +- templates/revisions_question.html | 3 +- templates/unanswered.html | 2 +- templates/user.html | 6 +- templates/user_edit.html | 2 +- templates/user_favorites.html | 2 +- templates/user_info.html | 2 + templates/user_preferences.html | 15 +- templates/user_recent.html | 2 +- templates/user_reputation.html | 2 +- templates/user_responses.html | 2 +- templates/user_stats.html | 2 +- templates/user_tabs.html | 2 - templates/user_votes.html | 2 +- urls.py | 3 + 74 files changed, 3131 insertions(+), 1955 deletions(-) delete mode 100644 locale/en/LC_MESSAGES/django.mo delete mode 100644 locale/es/LC_MESSAGES/django.mo delete mode 100644 locale/zh_CN/LC_MESSAGES/django.mo diff --git a/context.py b/context.py index f420b154..c068332c 100644 --- a/context.py +++ b/context.py @@ -1,9 +1,40 @@ from django.conf import settings def application_settings(context): - return { + my_settings = { 'APP_TITLE' : settings.APP_TITLE, 'APP_URL' : settings.APP_URL, 'APP_KEYWORDS' : settings.APP_KEYWORDS, 'APP_DESCRIPTION' : settings.APP_DESCRIPTION, - 'APP_INTRO' : settings.APP_INTRO + 'APP_INTRO' : settings.APP_INTRO, + 'EMAIL_VALIDATION': settings.EMAIL_VALIDATION, + 'LANGUAGE_CODE': settings.LANGUAGE_CODE, + 'GOOGLE_SITEMAP_CODE':settings.GOOGLE_SITEMAP_CODE, + 'GOOGLE_ANALYTICS_KEY':settings.GOOGLE_ANALYTICS_KEY, } + return {'settings':my_settings} + +def auth_processor(request): + """ + Returns context variables required by apps that use Django's authentication + system. + + If there is no 'user' attribute in the request, uses AnonymousUser (from + django.contrib.auth). + """ + if hasattr(request, 'user'): + user = request.user + if user.is_authenticated(): + messages = user.message_set.all() + else: + messages = None + else: + from django.contrib.auth.models import AnonymousUser + user = AnonymousUser() + messages = None + + from django.core.context_processors import PermWrapper + return { + 'user': user, + 'messages': messages, + 'perms': PermWrapper(user), + } diff --git a/development.log b/development.log index 6b96f8f5..5310f70b 100644 --- a/development.log +++ b/development.log @@ -1,4 +1,37 @@ -# development +==Aug 5, 2009 Evgeny== +===Interface changes=== +Merged in my code that: +* allows anonymous posting of Q&A and then login +* per-question email notifications via 'send_email_alerts' command +* allows space character in username +* improves openid login +* makes notification messages sticky - have to click "x" to dismiss +* unanswered questions are now those with no accepted answer +* added following setting parameters: + +settings.MIN_USERNAME_LENGTH = 1 +settings.EMAIL_UNIQUE = True|False +settings.EMAIL_VALIDATION = 'on'|'off' #thought of maybe adding other options so type is string +settings.GOOGLE_SITEMAP_CODE = +settings.GOOGLE_ANALYTICS_KEY = + +===Fixes=== +* fixed incorrect answer count issue in question.html +* translated Twittwer stuff in user_preferences.html +* translated question_retag.html, except one phrase +* added Adolfo's python2.4 fix +* fixed template debugging comments so that they don't break page layout +* reorganized header template so that it takes less vertical space + +===Code changes=== +* wrapped template context settings into a single settings dictionary +* on login anonymous session is recorded so that anonymously posted questions (if any) could be found later +* added models: AnonymousQuestion, AnonymousAnswer, EmailFeed (for notifications) +* User model has two new fields email_key - 32 byte hex hash and email_isvalid - boolean + file sql_scripts/update_2009_07_05_EF.sql will make upgrade to the live database +* added auth_processor to context.py which loads notification messages without deleting them + NOTE: default django auth processor must be removed! +* added ajax actions questionSubscribeUpdates/questionUnsubscribeUpdates ==Aug 4 2009, Evgeny== ===Changes=== diff --git a/django_authopenid/forms.py b/django_authopenid/forms.py index 09fa76b1..690a781f 100644 --- a/django_authopenid/forms.py +++ b/django_authopenid/forms.py @@ -158,7 +158,7 @@ class OpenidRegisterForm(forms.Form): raise forms.ValidationError(_('invalid user name')) if self.cleaned_data['username'] in RESERVED_NAMES: raise forms.ValidationError(_('sorry, this name can not be used, please try another')) - if len(self.cleaned_data['username']) < 3: + if len(self.cleaned_data['username']) < settings.MIN_USERNAME_LENGTH: raise forms.ValidationError(_('username too short')) try: user = User.objects.get( @@ -171,19 +171,22 @@ class OpenidRegisterForm(forms.Form): raise forms.ValidationError(_('this name is already in use - please try anoter')) def clean_email(self): - """For security reason one unique email in database""" + """Optionally, for security reason one unique email in database""" if 'email' in self.cleaned_data: - try: - user = User.objects.get(email = self.cleaned_data['email']) - except User.DoesNotExist: + if settings.EMAIL_UNIQUE == True: + try: + user = User.objects.get(email = self.cleaned_data['email']) + except User.DoesNotExist: + return self.cleaned_data['email'] + except User.MultipleObjectsReturned: + raise forms.ValidationError(u'There is already more than one \ + account registered with that e-mail address. Please try \ + another.') + raise forms.ValidationError(_("This email is already \ + registered in our database. Please choose another.")) + else: return self.cleaned_data['email'] - except User.MultipleObjectsReturned: - raise forms.ValidationError(u'There is already more than one \ - account registered with that e-mail address. Please try \ - another.') - raise forms.ValidationError(_("This email is already \ - registered in our database. Please choose another.")) - + #what if not??? class OpenidVerifyForm(forms.Form): """ openid verify form (associate an openid with an account) """ @@ -204,8 +207,7 @@ class OpenidVerifyForm(forms.Form): """ validate username """ if 'username' in self.cleaned_data: if not username_re.search(self.cleaned_data['username']): - raise forms.ValidationError(_("Usernames can only contain \ - letters, numbers and underscores")) + raise forms.ValidationError(_('invalid user name')) try: user = User.objects.get( username__exact = self.cleaned_data['username'] @@ -241,7 +243,7 @@ class OpenidVerifyForm(forms.Form): attrs_dict = { 'class': 'required' } -username_re = re.compile(r'^\w+$') +username_re = re.compile(r'^[\w ]+$') class RegistrationForm(forms.Form): """ legacy registration form """ @@ -286,17 +288,20 @@ class RegistrationForm(forms.Form): """ validate if email exist in database :return: raise error if it exist """ if 'email' in self.cleaned_data: - try: - user = User.objects.get(email = self.cleaned_data['email']) - except User.DoesNotExist: + if settings.EMAIL_UNIQUE == True: + try: + user = User.objects.get(email = self.cleaned_data['email']) + except User.DoesNotExist: + return self.cleaned_data['email'] + except User.MultipleObjectsReturned: + raise forms.ValidationError(u'There is already more than one \ + account registered with that e-mail address. Please try \ + another.') + raise forms.ValidationError(u'This email is already registered \ + in our database. Please choose another.') + else: return self.cleaned_data['email'] - except User.MultipleObjectsReturned: - raise forms.ValidationError(u'There is already more than one \ - account registered with that e-mail address. Please try \ - another.') - raise forms.ValidationError(u'This email is already registered \ - in our database. Please choose another.') - return self.cleaned_data['email'] + #what if not? def clean_password2(self): """ @@ -361,18 +366,21 @@ class ChangeemailForm(forms.Form): def clean_email(self): """ check if email don't exist """ if 'email' in self.cleaned_data: - if self.user.email != self.cleaned_data['email']: - try: - user = User.objects.get(email = self.cleaned_data['email']) - except User.DoesNotExist: - return self.cleaned_data['email'] - except User.MultipleObjectsReturned: - raise forms.ValidationError(u'There is already more than one \ - account registered with that e-mail address. Please try \ - another.') - raise forms.ValidationError(u'This email is already registered \ - in our database. Please choose another.') - return self.cleaned_data['email'] + if settings.EMAIL_UNIQUE == True: + if self.user.email != self.cleaned_data['email']: + try: + user = User.objects.get(email = self.cleaned_data['email']) + except User.DoesNotExist: + return self.cleaned_data['email'] + except User.MultipleObjectsReturned: + raise forms.ValidationError(u'There is already more than one \ + account registered with that e-mail address. Please try \ + another.') + raise forms.ValidationError(u'This email is already registered \ + in our database. Please choose another.') + else: + return self.cleaned_data['email'] + #what if not? def clean_password(self): diff --git a/django_authopenid/middleware.py b/django_authopenid/middleware.py index c0572c6e..2900d54c 100644 --- a/django_authopenid/middleware.py +++ b/django_authopenid/middleware.py @@ -21,4 +21,4 @@ class OpenIDMiddleware(object): mimeparse.best_match(['text/html', 'application/xrds+xml'], request.META['HTTP_ACCEPT']) == 'application/xrds+xml': return HttpResponseRedirect(reverse('yadis_xrdf')) - return response \ No newline at end of file + return response diff --git a/django_authopenid/urls.py b/django_authopenid/urls.py index 1ab65941..6843e5c0 100644 --- a/django_authopenid/urls.py +++ b/django_authopenid/urls.py @@ -7,6 +7,8 @@ urlpatterns = patterns('django_authopenid.views', url(r'^yadis.xrdf$', 'xrdf', name='yadis_xrdf'), # manage account registration url(r'^%s$' % _('signin/'), 'signin', name='user_signin'), + url(r'^%s%s$' % (_('signin/'),_('newquestion/')), 'signin', kwargs = {'newquestion':True}), + url(r'^%s%s$' % (_('signin/'),_('newanswer/')), 'signin', kwargs = {'newanswer':True}), url(r'^%s$' % _('signout/'), 'signout', name='user_signout'), url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin', name='user_complete_signin'), @@ -21,7 +23,8 @@ urlpatterns = patterns('django_authopenid.views', # manage account settings #url(r'^$', 'account_settings', name='user_account_settings'), #url(r'^%s$' % _('password/'), 'changepw', name='user_changepw'), - #url(r'^%s$' % _('email/'), 'changeemail', name='user_changeemail'), + url(r'^%s$' % 'email/', 'changeemail', name='user_changeemail',kwargs = {'action':'change'}), + url(r'^%s%s$' % ('email/','validate/'), 'changeemail', name='user_changeemail',kwargs = {'action':'validate'}), #url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'), url(r'^%s$' % _('delete/'), 'delete', name='user_delete'), ) diff --git a/django_authopenid/views.py b/django_authopenid/views.py index a9072c12..1cb8928b 100644 --- a/django_authopenid/views.py +++ b/django_authopenid/views.py @@ -30,12 +30,12 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from django.http import HttpResponseRedirect, get_host +from django.http import HttpResponseRedirect, get_host, Http404 from django.shortcuts import render_to_response as render from django.template import RequestContext, loader, Context from django.conf import settings from django.contrib.auth.models import User -from django.contrib.auth import login, logout +from django.contrib.auth import logout #for login I've added wrapper below - called login from django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse from django.utils.encoding import smart_unicode @@ -44,6 +44,7 @@ from django.utils.translation import ugettext as _ from django.contrib.sites.models import Site from django.utils.http import urlquote_plus from django.core.mail import send_mail +from django.views.defaults import server_error from openid.consumer.consumer import Consumer, \ SUCCESS, CANCEL, FAILURE, SETUP_NEEDED @@ -65,6 +66,16 @@ from django_authopenid.forms import OpenidSigninForm, OpenidAuthForm, OpenidRegi OpenidVerifyForm, RegistrationForm, ChangepwForm, ChangeemailForm, \ ChangeopenidForm, DeleteForm, EmailPasswordForm +def login(request,user): + from django.contrib.auth import login as _login + from forum.models import user_logged_in #custom signal + #1) get old session key + session_key = request.session.session_key + #2) login and get new session key + _login(request,user) + #3) send signal with old session key as argument + user_logged_in.send(user=user,session_key=session_key,sender=None) + def get_url_host(request): if request.is_secure(): protocol = 'https' @@ -76,8 +87,6 @@ def get_url_host(request): def get_full_url(request): return get_url_host(request) + request.get_full_path() - - def ask_openid(request, openid_url, redirect_to, on_failure=None, sreg_request=None): """ basic function to ask openid and return response """ @@ -96,7 +105,7 @@ def ask_openid(request, openid_url, redirect_to, on_failure=None, try: auth_request = consumer.begin(openid_url) except DiscoveryFailure: - msg = _(u"非法OpenID地址: %s" % openid_url) + msg = _(u"OpenID %(openid_url)s is invalid" % {'openid_url':openid_url}) return on_failure(request, msg) if sreg_request: @@ -113,7 +122,6 @@ def complete(request, on_success=None, on_failure=None, return_to=None): # make sure params are encoded in utf8 params = dict((k,smart_unicode(v)) for k, v in request.GET.items()) openid_response = consumer.complete(params, return_to) - if openid_response.status == SUCCESS: return on_success(request, openid_response.identity_url, @@ -150,7 +158,7 @@ def not_authenticated(func): return decorated @not_authenticated -def signin(request): +def signin(request,newquestion=False,newanswer=False): """ signin page. It manage the legacy authentification (user/password) and authentification with openid. @@ -168,7 +176,7 @@ def signin(request): if request.POST: - if 'bsignin' in request.POST.keys(): + if 'bsignin' in request.POST.keys() or 'openid_username' in request.POST.keys(): form_signin = OpenidSigninForm(request.POST) if form_signin.is_valid(): @@ -179,7 +187,6 @@ def signin(request): reverse('user_complete_signin'), urllib.urlencode({'next':next}) ) - return ask_openid(request, form_signin.cleaned_data['openid_url'], redirect_to, @@ -195,8 +202,24 @@ def signin(request): next = clean_next(form_auth.cleaned_data.get('next')) return HttpResponseRedirect(next) + question = None + if newquestion == True: + from forum.models import AnonymousQuestion as AQ + session_key = request.session.session_key + qlist = AQ.objects.filter(session_key=session_key).order_by('-added_at') + if len(qlist) > 0: + question = qlist[0] + answer = None + if newanswer == True: + from forum.models import AnonymousAnswer as AA + session_key = request.session.session_key + alist = AA.objects.filter(session_key=session_key).order_by('-added_at') + if len(alist) > 0: + answer = alist[0] return render('authopenid/signin.html', { + 'question':question, + 'answer':answer, 'form1': form_auth, 'form2': form_signin, 'msg': request.GET.get('msg',''), @@ -220,7 +243,7 @@ def signin_success(request, identity_url, openid_response): if openid isn't registered user is redirected to register page. """ - openid_ = from_openid_response(openid_response) + openid_ = from_openid_response(openid_response) #create janrain OpenID object request.session['openid'] = openid_ try: rel = UserAssociation.objects.get(openid_url__exact = str(openid_)) @@ -278,7 +301,8 @@ def register(request): 'next': next, 'username': nickname, }) - + + user_ = None if request.POST: just_completed = False if 'bnewaccount' in request.POST.keys(): @@ -309,14 +333,41 @@ def register(request): user_id=user_.id) uassoc.save() login(request, user_) + + #check if we need to post a question that was added anonymously + #this needs to be a function call becase this is also done + #if user just logged in and did not need to create the new account - # redirect, can redirect only if forms are valid. - if is_redirect: - return HttpResponseRedirect(next) + if user_ != None and settings.EMAIL_VALIDATION == 'on': + send_new_email_key(user_,nomessage=True) + output = validation_email_sent(request) + set_email_validation_message(user_) #message set after generating view + return output + elif user_.is_authenticated(): + return HttpResponseRedirect('/') + else: + raise server_error('') + openid_str = str(openid_) + bits = openid_str.split('/') + base_url = bits[2] #assume this is base url + url_bits = base_url.split('.') + provider_name = url_bits[-2].lower() + + providers = {'yahoo':'Yahoo!', + 'flickr':'flickr™', + 'google':'Google™', + 'aol':'AOL', + } + if provider_name not in providers: + provider_logo = provider_name + else: + provider_logo = providers[provider_name] + return render('authopenid/complete.html', { 'form1': form1, 'form2': form2, + 'provider':providers[provider_name], 'nickname': nickname, 'email': email }, context_instance=RequestContext(request)) @@ -464,47 +515,158 @@ def changepw(request): return render('authopenid/changepw.html', {'form': form }, context_instance=RequestContext(request)) +def find_email_validation_messages(user): + msg_text = _('your email needs to be validated') + return user.message_set.filter(message__exact=msg_text) + +def set_email_validation_message(user): + messages = find_email_validation_messages(user) + msg_text = _('your email needs to be validated') + if len(messages) == 0: + user.message_set.create(message=msg_text) + +def clear_email_validation_message(user): + messages = find_email_validation_messages(user) + messages.delete() + +def set_new_email(user, new_email, nomessage=False): + if new_email != user.email: + user.email = new_email + user.email_isvalid = False + user.save() + if settings.EMAIL_VALIDATION == 'on': + send_new_email_key(user,nomessage=nomessage) + +def _send_email_key(user): + """private function. sends email containing validation key + to user's email address + """ + subject = _("Welcome") + message_template = loader.get_template('authopenid/email_validation.txt') + import settings + message_context = Context({ + 'validation_link': '%s/email/verify/%d/%s/' % (settings.APP_URL ,user.id,user.email_key) + }) + message = message_template.render(message_context) + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]) + +def send_new_email_key(user,nomessage=False): + import random + random.seed() + user.email_key = '%032x' % random.getrandbits(128) + user.save() + _send_email_key(user) + if nomessage==False: + set_email_validation_message(user) + +@login_required +def send_email_key(request): + """ + url = /email/sendkey/ + + view that is shown right after sending email key + email sending is called internally + + raises 404 if email validation is off + if current email is valid shows 'key_not_sent' view of + authopenid/changeemail.html template + """ + + if settings.EMAIL_VALIDATION != 'off': + if request.user.email_isvalid: + return render('authopenid/changeemail.html', + { 'email': request.user.email, + 'action_type': 'key_not_sent', + 'change_link': reverse('user_changeemail')}, + context_instance=RequestContext(request) + ) + else: + _send_email_key(request.user) + return validation_email_sent(request) + else: + raise Http404 + + +#internal server view used as return value by other views +def validation_email_sent(request): + return render('authopenid/changeemail.html', + { 'email': request.user.email, 'action_type': 'validate', }, + context_instance=RequestContext(request)) + + +def verifyemail(request,id=None,key=None): + """ + view that is shown when user clicks email validation link + url = /email/verify/{{user.id}}/{{user.email_key}}/ + """ + if settings.EMAIL_VALIDATION != 'off': + user = User.objects.get(id=id) + if user: + if user.email_key == key: + user.email_isvalid = True + clear_email_validation_message(user) + user.save() + return render('authopenid/changeemail.html', { + 'action_type': 'validation_complete', + }, context_instance=RequestContext(request)) + raise Http404 + @login_required def changeemail(request): """ changeemail view. It require password or openid to allow change. - url: /changeemail/ + url: /email/* template : authopenid/changeemail.html """ msg = request.GET.get('msg', '') extension_args = {} user_ = request.user - + redirect_to = get_url_host(request) + reverse('user_changeemail') + action = 'change' if request.POST: form = ChangeemailForm(request.POST, user=user_) if form.is_valid(): if not form.test_openid: - user_.email = form.cleaned_data['email'] - user_.save() - msg = _("Email changed.") - redirect = "%s?msg=%s" % (reverse('user_account_settings'), - urlquote_plus(msg)) - return HttpResponseRedirect(redirect) + new_email = form.cleaned_data['email'] + if new_email != user_.email: + if settings.EMAIL_VALIDATION == 'on': + action = 'validate' + else: + action = 'done_novalidate' + set_new_email(user_, new_email,nomessage=True) + else: + action = 'keep' else: + #what does this branch do? + return server_error('') request.session['new_email'] = form.cleaned_data['email'] return ask_openid(request, form.cleaned_data['password'], redirect_to, on_failure=emailopenid_failure) + elif not request.POST and 'openid.mode' in request.GET: return complete(request, emailopenid_success, emailopenid_failure, redirect_to) else: form = ChangeemailForm(initial={'email': user_.email}, user=user_) + - return render('authopenid/changeemail.html', { + output = render('authopenid/changeemail.html', { 'form': form, + 'email': user_.email, + 'action_type': action, 'msg': msg }, context_instance=RequestContext(request)) + if action == 'validate': + set_email_validation_message(user_) + + return output + def emailopenid_success(request, identity_url, openid_response): openid_ = from_openid_response(openid_response) diff --git a/forum/admin.py b/forum/admin.py index 438a99e7..482da048 100644 --- a/forum/admin.py +++ b/forum/admin.py @@ -4,6 +4,9 @@ from django.contrib import admin from models import * +class AnonymousQuestionAdmin(admin.ModelAdmin): + """AnonymousQuestion admin class""" + class QuestionAdmin(admin.ModelAdmin): """Question admin class""" @@ -68,4 +71,4 @@ admin.site.register(Repute, ReputeAdmin) admin.site.register(Activity, ActivityAdmin) admin.site.register(Book, BookAdmin) admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin) -admin.site.register(BookAuthorRss, BookAuthorRssAdmin) \ No newline at end of file +admin.site.register(BookAuthorRss, BookAuthorRssAdmin) diff --git a/forum/feed.py b/forum/feed.py index 6374ba71..373f8a87 100644 --- a/forum/feed.py +++ b/forum/feed.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python #encoding:utf-8 #------------------------------------------------------------------------------- # Name: Syndication feed class for subsribtion @@ -13,16 +13,16 @@ from django.contrib.syndication.feeds import Feed, FeedDoesNotExist from django.utils.translation import ugettext as _ from models import Question +import settings class RssLastestQuestionsFeed(Feed): - title = _('site title') + _(' - ') + _('site slogan') + _(' - ')+ _('latest questions') - #EDIT!!! - link = 'http://where.com/questions/' - description = _('meta site content') + title = settings.APP_TITLE + _(' - ')+ _('latest questions') + link = settings.APP_URL + '/' + _('questions/') + description = settings.APP_DESCRIPTION #ttl = 10 - copyright = _('copyright message') + copyright = settings.APP_COPYRIGHT def item_link(self, item): - return '/questions/%s/' % item.id + return self.link + '%s/' % item.id def item_author_name(self, item): return item.author.username diff --git a/forum/forms.py b/forum/forms.py index 9d866720..59d0d620 100644 --- a/forum/forms.py +++ b/forum/forms.py @@ -43,7 +43,8 @@ class TagNamesField(forms.CharField): self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'}) self.max_length = 255 self.label = _('tags') - self.help_text = _('please use space to separate tags (this enables autocomplete feature)') + #self.help_text = _('please use space to separate tags (this enables autocomplete feature)') + self.help_text = _('Tags are short keywords, with no spaces within. Up to five tags can be used.') self.initial = '' def clean(self, value): @@ -74,6 +75,10 @@ class WikiField(forms.BooleanField): self.label = _('community wiki') self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown') +class EmailNotifyField(forms.BooleanField): + def __init__(self, *args, **kwargs): + super(EmailNotifyField, self).__init__(*args, **kwargs) + self.required = False class SummaryField(forms.CharField): def __init__(self, *args, **kwargs): @@ -94,18 +99,28 @@ class AskForm(forms.Form): user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) - - class AnswerForm(forms.Form): text = EditorField() wiki = WikiField() openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'})) user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) - def __init__(self, question, *args, **kwargs): + email_notify = EmailNotifyField() + def __init__(self, question, user, *args, **kwargs): super(AnswerForm, self).__init__(*args, **kwargs) + self.fields['email_notify'].widget.attrs['id'] = 'question-subscribe-updates'; if question.wiki: self.fields['wiki'].initial = True + if user.is_authenticated(): + try: + feed = EmailFeed.objects.get(feed_id=question.id, subscriber_id=user.id) + if feed.subscriber == user and feed.content == question: + self.fields['email_notify'].initial = True + return + except EmailFeed.DoesNotExist: + pass + self.fields['email_notify'].initial = False + class CloseForm(forms.Form): reason = forms.ChoiceField(choices=CLOSE_REASONS) @@ -181,13 +196,14 @@ class EditUserForm(forms.Form): def clean_email(self): """For security reason one unique email in database""" if self.user.email != self.cleaned_data['email']: - if 'email' in self.cleaned_data: - try: - user = User.objects.get(email = self.cleaned_data['email']) - except User.DoesNotExist: - return self.cleaned_data['email'] - except User.MultipleObjectsReturned: + #todo dry it, there is a similar thing in openidauth + if settings.EMAIL_UNIQUE == True: + if 'email' in self.cleaned_data: + try: + user = User.objects.get(email = self.cleaned_data['email']) + except User.DoesNotExist: + return self.cleaned_data['email'] + except User.MultipleObjectsReturned: + raise forms.ValidationError(_('this email has already been registered, please use another one')) raise forms.ValidationError(_('this email has already been registered, please use another one')) - raise forms.ValidationError(_('this email has already been registered, please use another one')) - else: - return self.cleaned_data['email'] + return self.cleaned_data['email'] diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py index 447e8971..03982c79 100644 --- a/forum/management/commands/once_award_badges.py +++ b/forum/management/commands/once_award_badges.py @@ -12,6 +12,7 @@ # Licence: GPL V2 #------------------------------------------------------------------------------- +from datetime import datetime, date from django.db import connection from django.shortcuts import get_object_or_404 from django.contrib.contenttypes.models import ContentType diff --git a/forum/models.py b/forum/models.py index b966ccb0..a6cb1697 100644 --- a/forum/models.py +++ b/forum/models.py @@ -12,16 +12,40 @@ from django.template.defaultfilters import slugify from django.db.models.signals import post_delete, post_save, pre_save from django.utils.translation import ugettext as _ import django.dispatch +import settings from forum.managers import * from const import * +class EmailFeed(models.Model): + #subscription key for unsubscribe by visiting emailed link + key = models.CharField(max_length=32) + #generic relation with feed content (i.e. question or tags) + feed_content_type = models.ForeignKey(ContentType,related_name='content_emailfeed') + feed_id = models.PositiveIntegerField() + content = generic.GenericForeignKey('feed_content_type','feed_id') + #generic relation with owner - either nameless email or User + subscriber_content_type = models.ForeignKey(ContentType,related_name='subscriber_emailfeed') + subscriber_id = models.PositiveIntegerField() + subscriber = generic.GenericForeignKey('subscriber_content_type','subscriber_id') + added_at = models.DateTimeField(default=datetime.datetime.now) + reported_at = models.DateTimeField(default=datetime.datetime.now) + + #getter functions rely on implementations of similar functions in content + #of subscriber objects + def get_update_summary(self): + return self.content.get_update_summary(last_reported_at = self.reported_at,recipient_email = self.get_email()) + + def get_email(self): + return self.subscriber.email + class Tag(models.Model): name = models.CharField(max_length=255, unique=True) created_by = models.ForeignKey(User, related_name='created_tags') deleted = models.BooleanField(default=False) deleted_at = models.DateTimeField(null=True, blank=True) deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags') + email_feeds = generic.GenericRelation(EmailFeed) # Denormalised data used_count = models.PositiveIntegerField(default=0) @@ -131,6 +155,7 @@ class Question(models.Model): comments = generic.GenericRelation(Comment) votes = generic.GenericRelation(Vote) flagged_items = generic.GenericRelation(FlaggedItem) + email_feeds = generic.GenericRelation(EmailFeed) objects = QuestionManager() @@ -173,7 +198,10 @@ class Question(models.Model): attr = CONST['deleted'] else: attr = None - return u'%s %s' % (self.title, attr) if attr is not None else self.title + if attr is not None: + return u'%s %s' % (self.title, attr) + else: + return self.title def get_revision_url(self): return reverse('question_revisions', args=[self.id]) @@ -181,6 +209,57 @@ class Question(models.Model): def get_latest_revision(self): return self.revisions.all()[0] + def get_update_summary(self,last_reported_at=None,recipient_email=''): + edited = False + if self.last_edited_at and self.last_edited_at > last_reported_at: + if self.last_edited_by.email != recipient_email: + edited = True + comments = [] + for comment in self.comments.all(): + if comment.added_at > last_reported_at and comment.user.email != recipient_email: + comments.append(comment) + new_answers = [] + answer_comments = [] + modified_answers = [] + for answer in self.answers.all(): + if (answer.added_at > last_reported_at): + new_answers.append(answer) + if (answer.last_edited_at + and answer.last_edited_at > last_reported_at + and answer.last_edited_by.email != recipient_email): + modified_answers.append(answer) + for comment in answer.comments.all(): + if comment.added_at > last_reported_at and comment.user.email != recipient_email: + answer_comments.append(comment) + if edited or comments or new_answers or modified_answers or answer_comments: + import sets + out = [] + if edited: + out.append(_('%(author)s modified the question') % {'author':self.last_edited_by.username}) + if new_answers: + names = sets.Set(map(lambda x: x.author.username,new_answers)) + people = ', '.join(names) + out.append(_('%(people)s posted %(new_answer_count)s new answers') \ + % {'new_answer_count':len(new_answers),'people':people}) + if comments: + names = sets.Set(map(lambda x: x.user.username,comments)) + people = ', '.join(names) + out.append(_('%(people)s commented the question') % {'people':people}) + if answer_comments: + names = sets.Set(map(lambda x: x.user.username,answer_comments)) + people = ', '.join(names) + if len(answer_comments) > 1: + out.append(_('%(people)s commented answers') % {'people':people}) + else: + out.append(_('%(people)s commented the answer') % {'people':people}) + url = settings.APP_URL + self.get_absolute_url() + retval = '%s:
\n' % (url,self.title) + out = map(lambda x: '
  • ' + x + '
  • ',out) + retval += '
      ' + '\n'.join(out) + '

    \n' + return retval + else: + return None + def __unicode__(self): return self.title @@ -219,6 +298,44 @@ class QuestionRevision(models.Model): def __unicode__(self): return u'revision %s of %s' % (self.revision, self.title) +class AnonymousAnswer(models.Model): + question = models.ForeignKey(Question, related_name='anonymous_answers') + session_key = models.CharField(max_length=40) #session id for anonymous questions + wiki = models.BooleanField(default=False) + added_at = models.DateTimeField(default=datetime.datetime.now) + ip_addr = models.IPAddressField(max_length=21) #allow high port numbers + author = models.ForeignKey(User,null=True) + text = models.TextField() + summary = models.CharField(max_length=180) + + def publish(self,user): + from forum.views import create_new_answer + added_at = datetime.datetime.now() + print user.id + create_new_answer(question=self.question,wiki=self.wiki, + added_at=added_at,text=self.text, + author=user) + self.delete() + +class AnonymousQuestion(models.Model): + title = models.CharField(max_length=300) + session_key = models.CharField(max_length=40) #session id for anonymous questions + text = models.TextField() + summary = models.CharField(max_length=180) + tagnames = models.CharField(max_length=125) + wiki = models.BooleanField(default=False) + added_at = models.DateTimeField(default=datetime.datetime.now) + ip_addr = models.IPAddressField(max_length=21) #allow high port numbers + author = models.ForeignKey(User,null=True) + + def publish(self,user): + from forum.views import create_new_question + added_at = datetime.datetime.now() + create_new_question(title=self.title, author=user, added_at=added_at, + wiki=self.wiki, tagnames=self.tagnames, + summary=self.summary, text=self.text) + self.delete() + class Answer(models.Model): question = models.ForeignKey(Question, related_name='answers') author = models.ForeignKey(User, related_name='answers') @@ -447,6 +564,13 @@ class BookAuthorRss(models.Model): class Meta: db_table = u'book_author_rss' +class AnonymousEmail(models.Model): + #validation key, if used + key = models.CharField(max_length=32) + email = models.EmailField(null=False,unique=True) + isvalid = models.BooleanField(default=False) + feeds = generic.GenericRelation(EmailFeed) + # User extend properties QUESTIONS_PER_PAGE_CHOICES = ( (10, u'10'), @@ -454,8 +578,11 @@ QUESTIONS_PER_PAGE_CHOICES = ( (50, u'50'), ) +User.add_to_class('email_isvalid', models.BooleanField(default=False)) +User.add_to_class('email_key', models.CharField(max_length=16, null=True)) User.add_to_class('reputation', models.PositiveIntegerField(default=1)) User.add_to_class('gravatar', models.CharField(max_length=32)) +User.add_to_class('email_feeds', generic.GenericRelation(EmailFeed)) User.add_to_class('favorite_questions', models.ManyToManyField(Question, through=FavoriteQuestion, related_name='favorited_by')) @@ -480,11 +607,14 @@ edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "mo delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"]) mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"]) user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"]) +user_logged_in = django.dispatch.Signal(providing_args=["session"]) + + def get_messages(self): - messages = [] - for m in self.message_set.all(): - messages.append(m.message) - return messages + messages = [] + for m in self.message_set.all(): + messages.append(m.message) + return messages def delete_messages(self): self.message_set.all().delete() @@ -632,6 +762,24 @@ def record_user_full_updated(instance, **kwargs): activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED) activity.save() +def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs): + aq_list = AnonymousQuestion.objects.filter(session_key = session_key) + aa_list = AnonymousAnswer.objects.filter(session_key = session_key) + import settings + if settings.EMAIL_VALIDATION == 'on':#add user to the record + for aq in aq_list: + aq.author = user + aq.save() + for aa in aa_list: + aa.author = user + aa.save() + #maybe add pending posts message? + else: #just publish the questions + for aq in aq_list: + aq.publish(user) + for aa in aa_list: + aa.publish(user) + #signal for User modle save changes pre_save.connect(calculate_gravatar_hash, sender=User) post_save.connect(record_ask_event, sender=Question) @@ -652,3 +800,4 @@ mark_offensive.connect(record_mark_offensive, sender=Answer) tags_updated.connect(record_update_tags, sender=Question) post_save.connect(record_favorite_question, sender=FavoriteQuestion) user_updated.connect(record_user_full_updated, sender=User) +user_logged_in.connect(post_stored_anonymous_content) diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py index 6c826771..ac4e6ca3 100644 --- a/forum/templatetags/extra_tags.py +++ b/forum/templatetags/extra_tags.py @@ -1,4 +1,4 @@ -import time +import time import datetime import math import re @@ -237,4 +237,4 @@ def get_latest_changed_timestamp(): timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest)) except: timestr = '' - return timestr \ No newline at end of file + return timestr diff --git a/forum/user.py b/forum/user.py index ed4494d6..41811db9 100644 --- a/forum/user.py +++ b/forum/user.py @@ -1,4 +1,3 @@ -# coding=utf-8 from django.utils.translation import ugettext as _ class UserView: def __init__(self, id, tab_title, tab_description, page_title, view_name, template_file, data_size=0): diff --git a/forum/views.py b/forum/views.py index 2c61d41e..98ecda11 100644 --- a/forum/views.py +++ b/forum/views.py @@ -168,46 +168,140 @@ def questions(request, tagname=None, unanswered=False): 'pagesize' : pagesize }}, context_instance=RequestContext(request)) +def create_new_answer( question=None, author=None,\ + added_at=None, wiki=False,\ + text='', email_notify=False): + + html = sanitize_html(markdowner.convert(text)) + + #create answer + answer = Answer( + question = question, + author = author, + added_at = added_at, + wiki = wiki, + html = html + ) + if answer.wiki: + answer.last_edited_by = answer.author + answer.last_edited_at = added_at + answer.wikified_at = added_at + + answer.save() + + #update question data + question.last_activity_at = added_at + question.last_activity_by = author + question.save() + Question.objects.update_answer_count(question) + + #update revision + AnswerRevision.objects.create( + answer = answer, + revision = 1, + author = author, + revised_at = added_at, + summary = CONST['default_version'], + text = text + ) + + #set notification/delete + if email_notify: + try: + EmailFeed.objects.get(feed_id = question.id, subscriber_id = author.id, feed_content_type=question_type) + except EmailFeed.DoesNotExist: + feed = EmailFeed(content = question, subscriber = author) + feed.save() + else: + #not sure if this is necessary. ajax should take care of this... + try: + feed = Email.objects.get(feed_id = question.id, subscriber_id = author.id, feed_content_type=question_type) + feed.delete() + except: + pass + +def create_new_question(title=None,author=None,added_at=None, + wiki=False,tagnames=None,summary=None, + text=None): + """this is not a view + and maybe should become one of the methods on Question object? + """ + html = sanitize_html(markdowner.convert(text)) + question = Question( + title = title, + author = author, + added_at = added_at, + last_activity_at = added_at, + last_activity_by = author, + wiki = wiki, + tagnames = tagnames, + html = html, + summary = summary + ) + if question.wiki: + question.last_edited_by = question.author + question.last_edited_at = added_at + question.wikified_at = added_at + + question.save() + + # create the first revision + QuestionRevision.objects.create( + question = question, + revision = 1, + title = question.title, + author = author, + revised_at = added_at, + tagnames = question.tagnames, + summary = CONST['default_version'], + text = text + ) + return question + #TODO: allow anynomus user to ask question by providing email and username. -@login_required +#@login_required def ask(request): if request.method == "POST": form = AskForm(request.POST) if form.is_valid(): - added_at = datetime.datetime.now() - html = sanitize_html(markdowner.convert(form.cleaned_data['text'])) - question = Question( - title = strip_tags(form.cleaned_data['title']), - author = request.user, - added_at = added_at, - last_activity_at = added_at, - last_activity_by = request.user, - wiki = form.cleaned_data['wiki'], - tagnames = form.cleaned_data['tags'].strip(), - html = html, - summary = strip_tags(html)[:120] - ) - if question.wiki: - question.last_edited_by = question.author - question.last_edited_at = added_at - question.wikified_at = added_at - - question.save() - # create the first revision - QuestionRevision.objects.create( - question = question, - revision = 1, - title = question.title, - author = request.user, - revised_at = added_at, - tagnames = question.tagnames, - summary = CONST['default_version'], - text = form.cleaned_data['text'] - ) + added_at = datetime.datetime.now() + title = strip_tags(form.cleaned_data['title']) + wiki = form.cleaned_data['wiki'] + tagnames = form.cleaned_data['tags'].strip() + text = form.cleaned_data['text'] + html = sanitize_html(markdowner.convert(text)) + summary = strip_tags(html)[:120] - return HttpResponseRedirect(question.get_absolute_url()) + if request.user.is_authenticated(): + author = request.user + + question = create_new_question( + title = title, + author = author, + added_at = added_at, + wiki = wiki, + tagnames = tagnames, + summary = summary, + text = text + ) + return HttpResponseRedirect(question.get_absolute_url()) + else: + request.session.flush() + session_key = request.session.session_key + question = AnonymousQuestion( + session_key = session_key, + title = title, + tagnames = tagnames, + wiki = wiki, + text = text, + summary = summary, + added_at = added_at, + ip_addr = request.META['REMOTE_ADDR'], + ) + question.save() + return HttpResponseRedirect('%s%s%s' % ( _('/account/'),_('signin/'),('newquestion/'))) else: form = AskForm() @@ -233,7 +327,7 @@ def question(request, id): question = get_object_or_404(Question, id=id) if question.deleted and not can_view_deleted_post(request.user, question): raise Http404 - answer_form = AnswerForm(question) + answer_form = AnswerForm(question,request.user) answers = Answer.objects.get_answers_from_question(question, request.user) answers = answers.select_related(depth=1) @@ -254,7 +348,16 @@ def question(request, id): if answers is not None: answers = answers.order_by("-accepted", orderby) - objects_list = Paginator(answers, ANSWERS_PAGE_SIZE) + + filtered_answers = [] + for answer in answers: + if answer.deleted == True: + if answer.author_id == request.user.id: + filtered_answers.append(answer) + else: + filtered_answers.append(answer) + + objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE) page_objects = objects_list.page(page) # update view count Question.objects.update_view_count(question) @@ -558,42 +661,38 @@ def answer_revisions(request, id): 'revisions': revisions, }, context_instance=RequestContext(request)) -#TODO: allow anynomus -@login_required def answer(request, id): question = get_object_or_404(Question, id=id) if request.method == "POST": - form = AnswerForm(question, request.POST) + form = AnswerForm(question, request.user, request.POST) if form.is_valid(): + wiki = form.cleaned_data['wiki'] + text = form.cleaned_data['text'] update_time = datetime.datetime.now() - answer = Answer( - question = question, - author = request.user, - added_at = update_time, - wiki = form.cleaned_data['wiki'], - html = sanitize_html(markdowner.convert(form.cleaned_data['text'])), - ) - if answer.wiki: - answer.last_edited_by = answer.author - answer.last_edited_at = update_time - answer.wikified_at = update_time - answer.save() - Question.objects.update_answer_count(question) - - question = get_object_or_404(Question, id=id) - question.last_activity_at = update_time - question.last_activity_by = request.user - question.save() - - AnswerRevision.objects.create( - answer = answer, - revision = 1, - author = request.user, - revised_at = update_time, - summary = CONST['default_version'], - text = form.cleaned_data['text'] - ) + if request.user.is_authenticated(): + create_new_answer( + question=question, + author=request.user, + added_at=update_time, + wiki=wiki, + text=text, + email_notify=form.cleaned_data['email_notify'] + ) + else: + request.session.flush() + html = sanitize_html(markdowner.convert(text)) + summary = strip_tags(html)[:120] + anon = AnonymousAnswer( + question = question, + wiki = wiki, + text = text, + summary = summary, + session_key = request.session.session_key, + ip_addr = request.META['REMOTE_ADDR'], + ) + anon.save() + return HttpResponseRedirect('/account/signin/newanswer') return HttpResponseRedirect(question.get_absolute_url()) @@ -655,6 +754,7 @@ def vote(request, id): offensiveAnswer:8, removeQuestion: 9, removeAnswer:10 + questionSubscribeUpdates:11 accept answer code: response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default @@ -831,6 +931,31 @@ def vote(request, id): else: onDeleted(post, request.user) delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user) + elif vote_type == '11':#subscribe q updates + user = request.user + if user.is_authenticated(): + try: + EmailFeed.objects.get(feed_id=question.id,subscriber_id=user.id,feed_content_type=question_type) + except EmailFeed.DoesNotExist: + feed = EmailFeed(subscriber=user,content=question) + feed.save() + if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False: + response_data['message'] = _('subscription saved, %(email)s needs validation') % {'email':user.email} + #response_data['status'] = 1 + #responst_data['allowed'] = 1 + else: + pass + #response_data['status'] = 0 + #response_data['allowed'] = 0 + elif vote_type == '12':#unsubscribe q updates + user = request.user + if user.is_authenticated(): + try: + feed = EmailFeed.objects.get(feed_id=question.id,subscriber_id=user.id) + feed.delete() + except EmailFeed.DoesNotExist: + pass + else: response_data['success'] = 0 response_data['message'] = u'Request mode is not supported. Please try again.' @@ -905,7 +1030,11 @@ def edit_user(request, id): if request.method == "POST": form = EditUserForm(user, request.POST) if form.is_valid(): - user.email = sanitize_html(form.cleaned_data['email']) + new_email = sanitize_html(form.cleaned_data['email']) + + from django_authopenid.views import set_new_email + set_new_email(user, new_email) + user.real_name = sanitize_html(form.cleaned_data['realname']) user.website = sanitize_html(form.cleaned_data['website']) user.location = sanitize_html(form.cleaned_data['city']) @@ -1498,7 +1627,6 @@ def user_reputation(request, user_id, user_view): reputation.query.group_by = ['question_id'] - rep_list = [] for rep in Repute.objects.filter(user=user).order_by('reputed_at'): dic = '[%s,%s]' % (calendar.timegm(rep.reputed_at.timetuple()) * 1000, rep.reputation) @@ -1683,7 +1811,6 @@ def read_message(request): if request.method == "POST": if request.POST['formdata'] == 'required': request.session['message_silent'] = 1 - if request.user.is_authenticated(): request.user.delete_messages() return HttpResponse('') diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo deleted file mode 100644 index a4dd5554..00000000 Binary files a/locale/en/LC_MESSAGES/django.mo and /dev/null differ diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index c4c1e674..cc42454c 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-06-22 20:40-0400\n" +"POT-Creation-Date: 2009-08-05 22:26-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -16,19 +16,21 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: settings.py:32 +#: settings.py:12 msgid "account/" msgstr "" -#: settings.py:32 django_authopenid/urls.py:9 django_authopenid/urls.py:11 +#: settings.py:12 django_authopenid/urls.py:9 django_authopenid/urls.py:10 +#: django_authopenid/urls.py:11 django_authopenid/urls.py:13 +#: forum/views.py:304 templates/authopenid/confirm_email.txt:10 msgid "signin/" msgstr "" -#: django_authopenid/forms.py:67 django_authopenid/views.py:93 +#: django_authopenid/forms.py:67 django_authopenid/views.py:102 msgid "i-names are not supported" msgstr "" -#: django_authopenid/forms.py:102 django_authopenid/forms.py:207 +#: django_authopenid/forms.py:102 msgid "" "Usernames can only contain letters, numbers and " "underscores" @@ -40,19 +42,19 @@ msgid "" "choose another." msgstr "" -#: django_authopenid/forms.py:126 django_authopenid/forms.py:231 +#: django_authopenid/forms.py:126 django_authopenid/forms.py:233 msgid "" "Please enter a valid username and password. Note that " "both fields are case-sensitive." msgstr "" -#: django_authopenid/forms.py:130 django_authopenid/forms.py:235 +#: django_authopenid/forms.py:130 django_authopenid/forms.py:237 msgid "This account is inactive." msgstr "" -#: django_authopenid/forms.py:158 +#: django_authopenid/forms.py:158 django_authopenid/forms.py:210 msgid "invalid user name" -msgstr "" +msgstr "User names can contain letters, underscore and empty space." #: django_authopenid/forms.py:160 msgid "sorry, this name can not be used, please try another" @@ -66,137 +68,147 @@ msgstr "" msgid "this name is already in use - please try anoter" msgstr "" -#: django_authopenid/forms.py:184 +#: django_authopenid/forms.py:185 msgid "" -"This email is already registered in our database. Please " -"choose another." +"This email is already registered in our database. " +"Please choose another." msgstr "" -#: django_authopenid/forms.py:214 +#: django_authopenid/forms.py:216 msgid "" "This username don't exist. Please choose another." msgstr "" -#: django_authopenid/forms.py:253 +#: django_authopenid/forms.py:255 msgid "choose a username" msgstr "" -#: django_authopenid/forms.py:255 templates/authopenid/signup.html:36 +#: django_authopenid/forms.py:257 templates/authopenid/signup.html:38 msgid "your email address" msgstr "" -#: django_authopenid/forms.py:257 templates/authopenid/signup.html:37 +#: django_authopenid/forms.py:259 templates/authopenid/signup.html:39 msgid "choose password" msgstr "" -#: django_authopenid/forms.py:259 templates/authopenid/signup.html:38 +#: django_authopenid/forms.py:261 templates/authopenid/signup.html:40 msgid "retype password" msgstr "" -#: django_authopenid/forms.py:330 +#: django_authopenid/forms.py:335 msgid "" "Old password is incorrect. Please enter the correct " "password." msgstr "" -#: django_authopenid/forms.py:342 +#: django_authopenid/forms.py:347 msgid "new passwords do not match" msgstr "" -#: django_authopenid/forms.py:434 +#: django_authopenid/forms.py:442 msgid "Incorrect username." msgstr "" #: django_authopenid/urls.py:10 -msgid "signout/" +msgid "newquestion/" msgstr "" #: django_authopenid/urls.py:11 -msgid "complete/" +msgid "newanswer/" +msgstr "" + +#: django_authopenid/urls.py:12 +msgid "signout/" msgstr "" #: django_authopenid/urls.py:13 +msgid "complete/" +msgstr "" + +#: django_authopenid/urls.py:15 msgid "register/" msgstr "" -#: django_authopenid/urls.py:14 +#: django_authopenid/urls.py:16 msgid "signup/" msgstr "" -#: django_authopenid/urls.py:16 +#: django_authopenid/urls.py:18 msgid "sendpw/" msgstr "" -#: django_authopenid/urls.py:26 +#: django_authopenid/urls.py:29 msgid "delete/" msgstr "" -#: django_authopenid/views.py:99 +#: django_authopenid/views.py:108 #, python-format -msgid "非法OpenID地址: %s" +msgid "OpenID %(openid_url)s is invalid" msgstr "" -#: django_authopenid/views.py:366 +#: django_authopenid/views.py:417 django_authopenid/views.py:544 msgid "Welcome" -msgstr "" +msgstr "Verification Email from NMR Wiki Q&A" -#: django_authopenid/views.py:456 +#: django_authopenid/views.py:507 msgid "Password changed." msgstr "" -#: django_authopenid/views.py:488 -msgid "Email changed." +#: django_authopenid/views.py:519 django_authopenid/views.py:524 +msgid "your email needs to be validated" msgstr "" +"Your email needs to be validated. Please see details here." -#: django_authopenid/views.py:519 django_authopenid/views.py:671 +#: django_authopenid/views.py:681 django_authopenid/views.py:833 #, python-format msgid "No OpenID %s found associated in our database" msgstr "" -#: django_authopenid/views.py:523 django_authopenid/views.py:678 +#: django_authopenid/views.py:685 django_authopenid/views.py:840 #, python-format msgid "The OpenID %s isn't associated to current user logged in" msgstr "" -#: django_authopenid/views.py:531 +#: django_authopenid/views.py:693 msgid "Email Changed." msgstr "" -#: django_authopenid/views.py:606 +#: django_authopenid/views.py:768 msgid "This OpenID is already associated with another account." msgstr "" -#: django_authopenid/views.py:611 +#: django_authopenid/views.py:773 #, python-format msgid "OpenID %s is now associated with your account." msgstr "" -#: django_authopenid/views.py:681 +#: django_authopenid/views.py:843 msgid "Account deleted." msgstr "" -#: django_authopenid/views.py:721 +#: django_authopenid/views.py:883 msgid "Request for new password" msgstr "" -#: django_authopenid/views.py:734 +#: django_authopenid/views.py:896 msgid "A new password has been sent to your email address." msgstr "" -#: django_authopenid/views.py:764 +#: django_authopenid/views.py:926 #, python-format msgid "" "Could not change password. Confirmation key '%s' is not " "registered." msgstr "" -#: django_authopenid/views.py:773 +#: django_authopenid/views.py:935 msgid "" "Can not change password. User don't exist anymore in our " "database." msgstr "" -#: django_authopenid/views.py:782 +#: django_authopenid/views.py:944 #, python-format msgid "Password changed for %s. You may now sign in." msgstr "" @@ -237,7 +249,7 @@ msgstr "" msgid "question" msgstr "" -#: forum/const.py:57 templates/book.html:110 templates/backup/book.html:110 +#: forum/const.py:57 templates/book.html:110 msgid "answer" msgstr "" @@ -259,7 +271,7 @@ msgstr "" #: forum/const.py:62 msgid "received award" -msgstr "" +msgstr "received badge" #: forum/const.py:63 msgid "marked best answer" @@ -317,43 +329,21 @@ msgstr "" msgid "retagged" msgstr "" -#: forum/feed.py:17 templates/base.html:7 templates/base_content.html:6 -#: templates/faq.html:25 templates/faq.html.py:108 -#: templates/backup/base.html:7 templates/backup/base_content.html:6 -#: templates/backup/faq.html:25 templates/backup/faq.html.py:108 -#: templates/tough/faq.html:23 templates/tough/faq.html.py:106 -#: templates/tough/question_retag.html:89 -msgid "site title" -msgstr "" - -#: forum/feed.py:17 +#: forum/feed.py:18 msgid " - " msgstr "" -#: forum/feed.py:17 templates/base.html:7 templates/base_content.html:6 -#: templates/backup/base.html:7 templates/backup/base_content.html:6 -msgid "site slogan" -msgstr "" - -#: forum/feed.py:17 +#: forum/feed.py:18 msgid "latest questions" msgstr "" -#: forum/feed.py:19 templates/index.html:8 templates/backup/index.html:8 -msgid "meta site content" -msgstr "" - -#: forum/feed.py:21 -msgid "copyright message" +#: forum/feed.py:19 +msgid "questions/" msgstr "" #: forum/forms.py:14 templates/answer_edit_tips.html:34 -#: templates/answer_edit_tips.html.py:39 templates/question_edit_tips.html:31 +#: templates/answer_edit_tips.html.py:38 templates/question_edit_tips.html:31 #: templates/question_edit_tips.html:36 -#: templates/backup/answer_edit_tips.html:33 -#: templates/backup/answer_edit_tips.html:38 -#: templates/backup/question_edit_tips.html:29 -#: templates/backup/question_edit_tips.html:34 msgid "title" msgstr "" @@ -374,108 +364,125 @@ msgid "question content must be > 10 characters" msgstr "" #: forum/forms.py:45 templates/header.html:30 templates/header.html.py:61 -#: templates/backup/header.html:30 templates/backup/header.html.py:59 msgid "tags" msgstr "" -#: forum/forms.py:46 -msgid "please use space to separate tags (this enables autocomplete feature)" +#: forum/forms.py:47 +msgid "" +"Tags are short keywords, with no spaces within. Up to five tags can be used." msgstr "" -#: forum/forms.py:53 +#: forum/forms.py:54 templates/question_retag.html:38 msgid "tags are required" msgstr "" -#: forum/forms.py:57 +#: forum/forms.py:58 msgid "please use 5 tags or less" msgstr "" -#: forum/forms.py:60 +#: forum/forms.py:61 msgid "tags must be shorter than 20 characters" msgstr "" -#: forum/forms.py:64 +#: forum/forms.py:65 msgid "" "please use following characters in tags: letters 'a-z', numbers, and " "characters '.-_#'" msgstr "" -#: forum/forms.py:74 templates/index.html:56 templates/question.html:196 -#: templates/question.html.py:377 templates/questions.html:58 +#: forum/forms.py:75 templates/index.html:57 templates/question.html:199 +#: templates/question.html.py:380 templates/questions.html:58 #: templates/questions.html.py:70 templates/unanswered.html:48 -#: templates/unanswered.html.py:60 templates/backup/index.html:56 -#: templates/backup/question.html:195 templates/backup/question.html.py:367 -#: templates/backup/questions.html:57 templates/backup/questions.html.py:69 -#: templates/backup/unanswered.html:47 templates/backup/unanswered.html:59 -#: templates/tough/unanswered.html:46 templates/tough/unanswered.html.py:58 +#: templates/unanswered.html.py:60 msgid "community wiki" msgstr "" -#: forum/forms.py:75 +#: forum/forms.py:76 msgid "" "if you choose community wiki option, the question and answer do not generate " "points and name of author will not be shown" msgstr "" -#: forum/forms.py:84 +#: forum/forms.py:89 msgid "update summary:" msgstr "" -#: forum/forms.py:85 +#: forum/forms.py:90 msgid "" "enter a brief summary of your revision (e.g. fixed spelling, grammar, " "improved style, this field is optional)" msgstr "" -#: forum/forms.py:160 +#: forum/forms.py:175 msgid "this email does not have to be linked to gravatar" msgstr "" -#: forum/forms.py:161 +#: forum/forms.py:176 msgid "Real name" msgstr "" -#: forum/forms.py:162 +#: forum/forms.py:177 msgid "Website" msgstr "" -#: forum/forms.py:163 +#: forum/forms.py:178 msgid "Location" msgstr "" -#: forum/forms.py:164 +#: forum/forms.py:179 msgid "Date of birth" msgstr "" -#: forum/forms.py:164 +#: forum/forms.py:179 msgid "will not be shown, used to calculate age, format: YYYY-MM-DD" msgstr "" -#: forum/forms.py:165 templates/authopenid/settings.html:20 +#: forum/forms.py:180 templates/authopenid/settings.html:21 msgid "Profile" msgstr "" -#: forum/forms.py:190 forum/forms.py:191 +#: forum/forms.py:207 forum/forms.py:208 msgid "this email has already been registered, please use another one" msgstr "" -#: forum/models.py:316 templates/badges.html:52 -#: templates/backup/badges.html:52 +#: forum/models.py:238 +#, python-format +msgid "%(author)s modified the question" +msgstr "" + +#: forum/models.py:242 +#, python-format +msgid "%(people)s posted %(new_answer_count)s new answers" +msgstr "" + +#: forum/models.py:247 +#, python-format +msgid "%(people)s commented the question" +msgstr "" + +#: forum/models.py:252 +#, python-format +msgid "%(people)s commented answers" +msgstr "" + +#: forum/models.py:254 +#, python-format +msgid "%(people)s commented the answer" +msgstr "" + +#: forum/models.py:433 templates/badges.html:52 msgid "gold" msgstr "" -#: forum/models.py:317 templates/badges.html:62 -#: templates/backup/badges.html:62 +#: forum/models.py:434 templates/badges.html:60 msgid "silver" msgstr "" -#: forum/models.py:318 templates/badges.html:70 -#: templates/backup/badges.html:70 +#: forum/models.py:435 templates/badges.html:67 msgid "bronze" msgstr "" #: forum/user.py:16 templates/user_tabs.html:7 -#: templates/backup/user_tabs.html:6 msgid "overview" msgstr "" @@ -488,7 +495,6 @@ msgid "user profile overview" msgstr "" #: forum/user.py:24 templates/user_tabs.html:9 -#: templates/backup/user_tabs.html:8 msgid "recent activity" msgstr "" @@ -501,12 +507,10 @@ msgid "profile - recent activity" msgstr "" #: forum/user.py:33 templates/user_tabs.html:13 -#: templates/backup/user_tabs.html:12 msgid "responses" msgstr "" #: forum/user.py:34 templates/user_tabs.html:12 -#: templates/backup/user_tabs.html:11 msgid "comments and answers to others questions" msgstr "" @@ -515,7 +519,6 @@ msgid "profile - responses" msgstr "" #: forum/user.py:42 templates/user_info.html:23 templates/users.html:26 -#: templates/backup/user_info.html:22 templates/backup/users.html:25 msgid "reputation" msgstr "" @@ -540,12 +543,10 @@ msgid "profile - favorite questions" msgstr "" #: forum/user.py:59 templates/user_tabs.html:20 -#: templates/backup/user_tabs.html:19 msgid "casted votes" msgstr "votes" #: forum/user.py:60 templates/user_tabs.html:20 -#: templates/backup/user_tabs.html:19 msgid "user vote record" msgstr "" @@ -557,8 +558,7 @@ msgstr "" msgid "preferences" msgstr "" -#: forum/user.py:69 templates/user_tabs.html:28 -#: templates/backup/user_tabs.html:27 +#: forum/user.py:69 templates/user_tabs.html:27 msgid "user preference settings" msgstr "" @@ -566,27 +566,42 @@ msgstr "" msgid "profile - user preferences" msgstr "" -#: forum/views.py:1730 +#: forum/views.py:304 +msgid "/account/" +msgstr "" + +#: forum/views.py:943 +#, python-format +msgid "subscription saved, %(email)s needs validation" +msgstr "" +"Your subscription is saved, but email address %(email)s needs to be " +"validated, please see more details here" + +#: forum/views.py:1853 msgid "uploading images is limited to users with >60 reputation points" msgstr "" -#: forum/views.py:1732 +#: forum/views.py:1855 msgid "allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'" msgstr "" -#: forum/views.py:1734 +#: forum/views.py:1857 #, python-format msgid "maximum upload file size is %sK" msgstr "" -#: forum/views.py:1736 +#: forum/views.py:1859 #, python-format msgid "" "Error uploading file. Please contact the site administrator. Thank you. %s" msgstr "" +#: forum/management/commands/send_email_alerts.py:35 +msgid "updates from website" +msgstr "" + #: forum/templatetags/extra_tags.py:139 forum/templatetags/extra_tags.py:168 -#: templates/header.html:33 templates/backup/header.html:33 +#: templates/header.html:33 msgid "badges" msgstr "" @@ -598,140 +613,121 @@ msgstr "" msgid " ago" msgstr "" -#: templates/404.html:24 templates/backup/404.html:24 +#: templates/404.html:24 msgid "Sorry, could not find the page you requested." msgstr "" -#: templates/404.html:26 templates/backup/404.html:26 +#: templates/404.html:26 msgid "This might have happened for the following reasons:" msgstr "" -#: templates/404.html:28 templates/backup/404.html:28 +#: templates/404.html:28 msgid "this question or answer has been deleted;" msgstr "" -#: templates/404.html:29 templates/backup/404.html:29 +#: templates/404.html:29 msgid "url has error - please check it;" msgstr "" -#: templates/404.html:30 templates/backup/404.html:30 +#: templates/404.html:30 msgid "" "the page you tried to visit is protected or you don't have sufficient " "points, see" msgstr "" -#: templates/404.html:31 templates/backup/404.html:31 +#: templates/404.html:31 msgid "if you believe this error 404 should not have occured, please" msgstr "" -#: templates/404.html:32 templates/backup/404.html:32 +#: templates/404.html:32 msgid "report this problem" msgstr "" -#: templates/404.html:41 templates/500.html:27 templates/backup/404.html:41 -#: templates/backup/500.html:27 +#: templates/404.html:41 templates/500.html:27 msgid "back to previous page" msgstr "" -#: templates/404.html:42 templates/backup/404.html:42 +#: templates/404.html:42 msgid "see all questions" msgstr "" -#: templates/404.html:43 templates/backup/404.html:43 +#: templates/404.html:43 msgid "see all tags" msgstr "" -#: templates/500.html:24 templates/backup/500.html:24 +#: templates/500.html:22 +msgid "sorry, system error" +msgstr "" + +#: templates/500.html:24 msgid "system error log is recorded, error will be fixed as soon as possible" msgstr "" -#: templates/500.html:25 templates/backup/500.html:25 +#: templates/500.html:25 msgid "please report the error to the site administrators if you wish" msgstr "" -#: templates/500.html:28 templates/backup/500.html:28 +#: templates/500.html:28 msgid "see latest questions" msgstr "" -#: templates/500.html:29 templates/backup/500.html:29 +#: templates/500.html:29 msgid "see tags" msgstr "" #: templates/about.html:6 templates/about.html.py:11 -#: templates/backup/about.html:6 templates/backup/about.html.py:11 msgid "About" msgstr "" #: templates/answer_edit.html:4 templates/answer_edit.html.py:47 -#: templates/backup/answer_edit.html:3 templates/backup/answer_edit.html:46 msgid "Edit answer" msgstr "" #: templates/answer_edit.html:24 templates/answer_edit.html.py:27 -#: templates/ask.html:25 templates/ask.html.py:28 templates/question.html:37 -#: templates/question.html.py:40 templates/question_edit.html:27 -#: templates/backup/answer_edit.html:23 templates/backup/answer_edit.html:26 -#: templates/backup/ask.html:24 templates/backup/ask.html.py:27 -#: templates/backup/question.html:36 templates/backup/question.html.py:39 -#: templates/backup/question_edit.html:25 +#: templates/ask.html:25 templates/ask.html.py:28 templates/question.html:40 +#: templates/question.html.py:43 templates/question_edit.html:27 msgid "hide preview" msgstr "" #: templates/answer_edit.html:27 templates/ask.html:28 -#: templates/question.html:40 templates/question_edit.html:27 -#: templates/backup/answer_edit.html:26 templates/backup/ask.html:27 -#: templates/backup/question.html:39 templates/backup/question_edit.html:25 +#: templates/question.html:43 templates/question_edit.html:27 msgid "show preview" msgstr "" #: templates/answer_edit.html:47 templates/question_edit.html:65 -#: templates/revisions_answer.html:36 templates/revisions_question.html:36 -#: templates/backup/answer_edit.html:46 templates/backup/question_edit.html:63 -#: templates/backup/revisions_answer.html:35 -#: templates/backup/revisions_question.html:34 -#: templates/tough/question_retag.html:51 +#: templates/question_retag.html:52 templates/revisions_answer.html:36 +#: templates/revisions_question.html:36 msgid "back" msgstr "" #: templates/answer_edit.html:52 templates/question_edit.html:70 #: templates/revisions_answer.html:47 templates/revisions_question.html:47 -#: templates/backup/answer_edit.html:51 templates/backup/question_edit.html:68 -#: templates/backup/revisions_answer.html:46 -#: templates/backup/revisions_question.html:45 msgid "revision" msgstr "" #: templates/answer_edit.html:55 templates/question_edit.html:74 -#: templates/backup/answer_edit.html:54 templates/backup/question_edit.html:72 msgid "select revision" msgstr "" -#: templates/answer_edit.html:62 templates/ask.html:81 -#: templates/question.html:447 templates/question_edit.html:91 -#: templates/backup/answer_edit.html:61 templates/backup/ask.html:80 -#: templates/backup/question.html:437 templates/backup/question_edit.html:89 +#: templates/answer_edit.html:62 templates/ask.html:94 +#: templates/question.html:452 templates/question_edit.html:91 msgid "Toggle the real time Markdown editor preview" msgstr "" -#: templates/answer_edit.html:62 templates/ask.html:81 -#: templates/question.html:447 templates/question_edit.html:91 -#: templates/backup/answer_edit.html:61 templates/backup/ask.html:80 -#: templates/backup/question.html:437 templates/backup/question_edit.html:89 +#: templates/answer_edit.html:62 templates/ask.html:94 +#: templates/question.html:452 templates/question_edit.html:91 msgid "toggle preview" msgstr "" #: templates/answer_edit.html:73 templates/question_edit.html:119 -#: templates/backup/answer_edit.html:72 -#: templates/backup/question_edit.html:117 +#: templates/question_retag.html:75 msgid "Save edit" msgstr "" #: templates/answer_edit.html:74 templates/close.html:29 -#: templates/question_edit.html:120 templates/reopen.html:30 -#: templates/user_edit.html:83 templates/backup/answer_edit.html:73 -#: templates/backup/close.html:29 templates/backup/question_edit.html:118 -#: templates/backup/reopen.html:28 templates/backup/user_edit.html:81 -#: templates/tough/question_retag.html:75 +#: templates/question_edit.html:120 templates/question_retag.html:76 +#: templates/reopen.html:30 templates/user_edit.html:83 +#: templates/authopenid/changeemail.html:34 msgid "Cancel" msgstr "" @@ -739,726 +735,613 @@ msgstr "" msgid "answer tips" msgstr "Tips" -#: templates/answer_edit_tips.html:7 templates/backup/answer_edit_tips.html:6 +#: templates/answer_edit_tips.html:7 msgid "please make your answer relevant to this community" msgstr "" -#: templates/answer_edit_tips.html:10 templates/backup/answer_edit_tips.html:9 +#: templates/answer_edit_tips.html:10 msgid "try to give an answer, rather than engage into a discussion" msgstr "" #: templates/answer_edit_tips.html:13 -#: templates/backup/answer_edit_tips.html:12 msgid "please try to provide details" msgstr "" #: templates/answer_edit_tips.html:16 templates/question_edit_tips.html:13 -#: templates/backup/answer_edit_tips.html:15 -#: templates/backup/question_edit_tips.html:11 msgid "be clear and concise" msgstr "" #: templates/answer_edit_tips.html:19 templates/question_edit_tips.html:16 -#: templates/backup/answer_edit_tips.html:18 -#: templates/backup/question_edit_tips.html:14 msgid "see frequently asked questions" msgstr "" #: templates/answer_edit_tips.html:25 templates/question_edit_tips.html:22 -#: templates/backup/answer_edit_tips.html:24 -#: templates/backup/question_edit_tips.html:20 msgid "Markdown tips" msgstr "Markdown basics" #: templates/answer_edit_tips.html:28 templates/question_edit_tips.html:25 -#: templates/backup/answer_edit_tips.html:27 -#: templates/backup/question_edit_tips.html:23 msgid "*italic* or __italic__" msgstr "" #: templates/answer_edit_tips.html:31 templates/question_edit_tips.html:28 -#: templates/backup/answer_edit_tips.html:30 -#: templates/backup/question_edit_tips.html:26 msgid "**bold** or __bold__" msgstr "" #: templates/answer_edit_tips.html:34 templates/question_edit_tips.html:31 -#: templates/backup/answer_edit_tips.html:33 -#: templates/backup/question_edit_tips.html:29 msgid "link" msgstr "" -#: templates/answer_edit_tips.html:34 templates/answer_edit_tips.html.py:39 +#: templates/answer_edit_tips.html:34 templates/answer_edit_tips.html.py:38 #: templates/question_edit_tips.html:31 templates/question_edit_tips.html:36 -#: templates/backup/answer_edit_tips.html:33 -#: templates/backup/answer_edit_tips.html:38 -#: templates/backup/question_edit_tips.html:29 -#: templates/backup/question_edit_tips.html:34 msgid "text" msgstr "" -#: templates/answer_edit_tips.html:39 templates/question_edit_tips.html:36 -#: templates/backup/answer_edit_tips.html:38 -#: templates/backup/question_edit_tips.html:34 +#: templates/answer_edit_tips.html:38 templates/question_edit_tips.html:36 msgid "image" msgstr "" -#: templates/answer_edit_tips.html:43 templates/question_edit_tips.html:40 -#: templates/backup/answer_edit_tips.html:42 -#: templates/backup/question_edit_tips.html:38 +#: templates/answer_edit_tips.html:42 templates/question_edit_tips.html:40 msgid "numbered list:" msgstr "" -#: templates/answer_edit_tips.html:48 templates/question_edit_tips.html:45 -#: templates/backup/answer_edit_tips.html:47 -#: templates/backup/question_edit_tips.html:43 +#: templates/answer_edit_tips.html:47 templates/question_edit_tips.html:45 msgid "basic HTML tags are also supported" msgstr "" -#: templates/answer_edit_tips.html:51 templates/question_edit_tips.html:48 -#: templates/backup/answer_edit_tips.html:50 -#: templates/backup/question_edit_tips.html:46 +#: templates/answer_edit_tips.html:50 templates/question_edit_tips.html:48 msgid "learn more about Markdown" msgstr "" -#: templates/ask.html:4 templates/ask.html.py:60 templates/backup/ask.html:3 -#: templates/backup/ask.html.py:59 +#: templates/ask.html:4 templates/ask.html.py:60 msgid "Ask a question" msgstr "" -#: templates/ask.html:106 templates/backup/ask.html:105 -msgid "Use" -msgstr "" - -#: templates/ask.html:106 templates/backup/ask.html:105 -msgid "learn more about OpenID" +#: templates/ask.html:67 +msgid "login to post question info" msgstr "" +"You are welcome to start submitting your question " +"anonymously - you are currently not logged in. When you post your " +"question, you will be redirected to the login/signup page. Your question " +"will be saved meanwhile and will be posted when you log in. Login/signup " +"process is very simple. Login takes about 30 seconds, initial signup takes a " +"minute or less." -#: templates/ask.html:106 templates/authopenid/signin.html:34 -#: templates/authopenid/signin.html:60 templates/backup/ask.html:105 -msgid "Login" -msgstr "" - -#: templates/ask.html:109 templates/backup/ask.html:108 -msgid "Get your own " +#: templates/ask.html:73 +#, python-format +msgid "must have valid %(email)s to post" msgstr "" +"Looks like your email address, %(email)s has not " +"yet been validated. To post messages you must verify your email, " +"please see more details here.
    You can submit " +"your question now and validate email after that. Your question will saved as " +"pending meanwhile. " -#: templates/ask.html:117 templates/authopenid/sendpw.html:27 -#: templates/backup/ask.html:116 -msgid "User name" +#: templates/ask.html:108 +msgid "(required)" msgstr "" -#: templates/ask.html:120 templates/backup/ask.html:119 -msgid "Email: (won't be shown to anyone)" -msgstr "" +#: templates/ask.html:115 +msgid "Login/signup to post your question" +msgstr "Login/Signup to Post" -#: templates/ask.html:127 templates/backup/ask.html:126 +#: templates/ask.html:117 msgid "Ask your question" -msgstr "" +msgstr "Ask Your Question" #: templates/badge.html:6 templates/badge.html.py:17 -#: templates/backup/badge.html:5 templates/backup/badge.html.py:16 msgid "Badge" msgstr "" -#: templates/badge.html:26 templates/backup/badge.html:25 +#: templates/badge.html:26 msgid "The users have been awarded with badges:" msgstr "" -#: templates/badges.html:6 templates/backup/badges.html:6 +#: templates/badges.html:6 msgid "Badges summary" msgstr "" #: templates/badges.html:17 templates/user_stats.html:113 -#: templates/backup/badges.html:17 templates/backup/user_stats.html:112 msgid "Badges" msgstr "" -#: templates/badges.html:21 templates/backup/badges.html:21 +#: templates/badges.html:21 msgid "Community gives you awards for your questions, answers and votes." msgstr "" +"If your questions and answers are highly voted, your contribution to this " +"Q&A community will be recognized with the variety of badges." -#: templates/badges.html:22 templates/backup/badges.html:22 +#: templates/badges.html:22 msgid "" "Below is the list of available badges and number of times each type of badge " "has been awarded." msgstr "" +"Currently badges differ only by their level: gold, " +"silver and bronze (their meanings are " +"described on the right). In the future there will be many types of badges at " +"each level. Please give us your feedback - what kinds of badges would you like to see and suggest the " +"activity for which those badges might be awarded." -#: templates/badges.html:49 templates/backup/badges.html:49 +#: templates/badges.html:49 msgid "Community badges" -msgstr "" +msgstr "Badge levels" -#: templates/badges.html:55 templates/backup/badges.html:55 -msgid "Gold badge is very rare." +#: templates/badges.html:55 +msgid "gold badge description" msgstr "" +"Gold badge is the highest award in this community. To obtain it have to show " +"profound knowledge and ability in addition to your active participation." -#: templates/badges.html:56 templates/backup/badges.html:56 -msgid "" -"To obtain it you have to show profound knowledge and ability in addition to " -"actively participating in the community." +#: templates/badges.html:63 +msgid "silver badge description" msgstr "" +"Obtaining silver badge requires significant patience. If you have received " +"one, that means you have greatly contributed to this community." -#: templates/badges.html:57 templates/backup/badges.html:57 -msgid "Gold badge is the highest award in this community." -msgstr "" - -#: templates/badges.html:65 templates/backup/badges.html:65 -msgid "Obtaining silver badge requires significant patience." -msgstr "" - -#: templates/badges.html:66 templates/backup/badges.html:66 -msgid "If you got one, you've very significantly contributed to this community" -msgstr "" - -#: templates/badges.html:69 templates/backup/badges.html:69 +#: templates/badges.html:66 msgid "bronze badge: often given as a special honor" msgstr "" -#: templates/badges.html:73 templates/backup/badges.html:73 -msgid "" -"If you are active in this community, you will will get this medal - still it " -"is a special honor." +#: templates/badges.html:70 +msgid "bronze badge description" msgstr "" +"If you are an active participant in this community, you will be recognized " +"with this badge." -#: templates/base.html:59 templates/base_content.html:60 -#: templates/backup/base.html:59 templates/backup/base_content.html:57 -msgid "congratulations, community gave you a badge" -msgstr "" - -#: templates/base.html:61 templates/base_content.html:62 -#: templates/backup/base.html:61 templates/backup/base_content.html:59 -msgid "profile" -msgstr "" - -#: templates/base_content.html:61 templates/backup/base_content.html:58 -msgid "see" -msgstr "" - -#: templates/book.html:7 templates/backup/book.html:7 +#: templates/book.html:7 msgid "reading channel" msgstr "" -#: templates/book.html:26 templates/backup/book.html:26 +#: templates/book.html:26 msgid "[author]" msgstr "" -#: templates/book.html:30 templates/backup/book.html:30 +#: templates/book.html:30 msgid "[publisher]" msgstr "" -#: templates/book.html:34 templates/backup/book.html:34 +#: templates/book.html:34 msgid "[publication date]" msgstr "" -#: templates/book.html:38 templates/backup/book.html:38 +#: templates/book.html:38 msgid "[price]" msgstr "" -#: templates/book.html:39 templates/backup/book.html:39 +#: templates/book.html:39 msgid "currency unit" msgstr "" -#: templates/book.html:42 templates/backup/book.html:42 +#: templates/book.html:42 msgid "[pages]" msgstr "" -#: templates/book.html:43 templates/backup/book.html:43 +#: templates/book.html:43 msgid "pages abbreviation" msgstr "" -#: templates/book.html:46 templates/backup/book.html:46 +#: templates/book.html:46 msgid "[tags]" msgstr "" -#: templates/book.html:56 templates/backup/book.html:56 +#: templates/book.html:56 msgid "author blog" msgstr "" -#: templates/book.html:62 templates/backup/book.html:62 +#: templates/book.html:62 msgid "book directory" msgstr "" -#: templates/book.html:66 templates/backup/book.html:66 +#: templates/book.html:66 msgid "buy online" msgstr "" -#: templates/book.html:79 templates/backup/book.html:79 +#: templates/book.html:79 msgid "reader questions" msgstr "" -#: templates/book.html:82 templates/backup/book.html:82 +#: templates/book.html:82 msgid "ask the author" msgstr "" #: templates/book.html:88 templates/book.html.py:93 -#: templates/users_questions.html:17 templates/backup/book.html:88 -#: templates/backup/book.html.py:93 templates/backup/users_questions.html:16 +#: templates/users_questions.html:17 msgid "this question was selected as favorite" msgstr "" #: templates/book.html:88 templates/book.html.py:93 #: templates/users_questions.html:11 templates/users_questions.html.py:17 -#: templates/backup/book.html:88 templates/backup/book.html.py:93 -#: templates/backup/users_questions.html:10 -#: templates/backup/users_questions.html:16 msgid "number of times" msgstr "" -#: templates/book.html:105 templates/index.html:47 templates/questions.html:46 +#: templates/book.html:105 templates/index.html:48 templates/questions.html:46 #: templates/unanswered.html:37 templates/users_questions.html:30 -#: templates/backup/book.html:105 templates/backup/index.html:47 -#: templates/backup/questions.html:45 templates/backup/unanswered.html:36 -#: templates/backup/users_questions.html:29 templates/tough/unanswered.html:35 msgid "votes" msgstr "" -#: templates/book.html:108 templates/backup/book.html:108 +#: templates/book.html:108 msgid "the answer has been accepted to be correct" msgstr "" -#: templates/book.html:115 templates/index.html:48 templates/questions.html:47 +#: templates/book.html:115 templates/index.html:49 templates/questions.html:47 #: templates/unanswered.html:38 templates/users_questions.html:40 -#: templates/backup/book.html:115 templates/backup/index.html:48 -#: templates/backup/questions.html:46 templates/backup/unanswered.html:37 -#: templates/backup/users_questions.html:39 templates/tough/unanswered.html:36 msgid "views" msgstr "" -#: templates/book.html:125 templates/index.html:68 templates/question.html:112 -#: templates/question.html.py:479 templates/questions.html:84 -#: templates/questions.html.py:149 templates/tags.html:47 +#: templates/book.html:125 templates/index.html:69 templates/question.html:115 +#: templates/question.html.py:486 templates/questions.html:84 +#: templates/questions.html.py:156 templates/tags.html:47 #: templates/unanswered.html:75 templates/unanswered.html.py:109 -#: templates/users_questions.html:52 templates/backup/book.html:125 -#: templates/backup/index.html:68 templates/backup/index.html.py:93 -#: templates/backup/question.html:111 templates/backup/question.html.py:469 -#: templates/backup/questions.html:83 templates/backup/questions.html:148 -#: templates/backup/tags.html:46 templates/backup/unanswered.html:74 -#: templates/backup/unanswered.html:108 -#: templates/backup/users_questions.html:51 templates/tough/unanswered.html:72 -#: templates/tough/unanswered.html:105 +#: templates/users_questions.html:52 msgid "using tags" msgstr "" -#: templates/book.html:147 templates/backup/book.html:147 +#: templates/book.html:147 msgid "subscribe to book RSS feed" msgstr "" #: templates/book.html:147 templates/index.html:116 -#: templates/backup/book.html:147 templates/backup/index.html:115 msgid "subscribe to the questions feed" msgstr "" #: templates/close.html:6 templates/close.html.py:16 -#: templates/backup/close.html:6 templates/backup/close.html.py:16 msgid "Close question" msgstr "" -#: templates/close.html:19 templates/backup/close.html:19 +#: templates/close.html:19 msgid "Close the question" msgstr "" -#: templates/close.html:25 templates/backup/close.html:25 +#: templates/close.html:25 msgid "Reasons" msgstr "" -#: templates/close.html:28 templates/backup/close.html:28 +#: templates/close.html:28 msgid "OK to close" msgstr "" -#: templates/faq.html:11 templates/backup/faq.html:11 -#: templates/tough/faq.html:9 +#: templates/faq.html:11 msgid "Frequently Asked Questions " msgstr "" -#: templates/faq.html:15 templates/backup/faq.html:15 -#: templates/tough/faq.html:13 +#: templates/faq.html:16 msgid "What kinds of questions can I ask here?" msgstr "" -#: templates/faq.html:16 templates/backup/faq.html:16 -#: templates/tough/faq.html:14 +#: templates/faq.html:17 msgid "" "Most importanly - questions should be relevant to this " "community." msgstr "" +"Please ask questions about all aspects of Magnetic Resonance - NMR, EPR, MRI. " +"Questions about both theory and practice in these fields are welcome, " +"including questions about the physical foundations, scientific applications, " +"instrumentation, interpretation of data, software, preparation and handling " +"of samples etc. " -#: templates/faq.html:17 templates/backup/faq.html:17 -#: templates/tough/faq.html:15 +#: templates/faq.html:18 msgid "" "Before asking the question - please make sure to use search to see whether " "your question has alredy been answered." msgstr "" +"Before you ask - please make sure to search for a similar question. You can " +"search questions by their title or tags." -#: templates/faq.html:20 templates/backup/faq.html:20 -#: templates/tough/faq.html:18 +#: templates/faq.html:21 msgid "What questions should I avoid asking?" -msgstr "" +msgstr "What kinds of questions should be avoided?" -#: templates/faq.html:21 templates/backup/faq.html:21 -#: templates/tough/faq.html:19 +#: templates/faq.html:22 msgid "" "Please avoid asking questions that are not relevant to this community, too " "subjective and argumentative." msgstr "" +"Please avoid asking questions that are not relevant to the subjects of NMR, " +"EPR or MRI, also please avoid asking questions that are too argumentative or " +"subjective." -#: templates/faq.html:24 templates/backup/faq.html:24 -#: templates/tough/faq.html:22 +#: templates/faq.html:27 msgid "What should I avoid in my answers?" msgstr "" -#: templates/faq.html:25 templates/backup/faq.html:25 -#: templates/tough/faq.html:23 +#: templates/faq.html:28 templates/faq.html.py:132 +msgid "site title" +msgstr "NMR Wiki Q&A" + +#: templates/faq.html:28 msgid "" "is a Q&A site, not a discussion group. Therefore - please avoid having " "discussions in your answers, comment facility allows some space for brief " "discussions." msgstr "" +"is a question and answer site - it is not a " +"discussion group. Please avoid holding debates in your answers as " +"they tend to dilute the essense of questions and answers. For the brief " +"discussions please use commenting facility." -#: templates/faq.html:28 templates/backup/faq.html:28 -#: templates/tough/faq.html:26 +#: templates/faq.html:32 msgid "Who moderates this community?" msgstr "" -#: templates/faq.html:29 templates/backup/faq.html:29 -#: templates/tough/faq.html:27 +#: templates/faq.html:33 msgid "The short answer is: you." msgstr "" -#: templates/faq.html:30 templates/backup/faq.html:30 -#: templates/tough/faq.html:28 +#: templates/faq.html:34 msgid "This website is moderated by the users." msgstr "" -#: templates/faq.html:31 templates/backup/faq.html:31 -#: templates/tough/faq.html:29 +#: templates/faq.html:35 msgid "" "The reputation system allows users earn the authorization to perform a " "variety of moderation tasks." msgstr "" -#: templates/faq.html:34 templates/backup/faq.html:34 -#: templates/tough/faq.html:32 +#: templates/faq.html:40 msgid "How does reputation system work?" msgstr "" -#: templates/faq.html:35 templates/backup/faq.html:35 -#: templates/tough/faq.html:33 -msgid "" -"Anyone can ask questions and give answers, points are not necessary for that." +#: templates/faq.html:41 +msgid "Rep system summary" msgstr "" +"When a question or answer is upvoted, the user who posted them will gain " +"some points, which are called \"reputation points\". These points serve as a " +"rough measure of the community trust to him/her. Various moderation tasks " +"are gradually assigned to the users based on those points." -#: templates/faq.html:36 templates/backup/faq.html:36 -#: templates/tough/faq.html:34 -msgid "" -"As we've said before, users help running this site. Point system helps " -"select users who can administer this community." -msgstr "" - -#: templates/faq.html:37 templates/backup/faq.html:37 -#: templates/tough/faq.html:35 -msgid "" -"Reputation points roughly measure how community trusts you. These points are " -"given to you directly by other members of the community." -msgstr "" - -#: templates/faq.html:40 templates/backup/faq.html:40 -#: templates/tough/faq.html:38 -msgid "" -"For example, if you ask an interesting question or give a helpful answer, " -"your input will be upvoted and you will gain more trust in the community." -msgstr "" - -#: templates/faq.html:41 templates/backup/faq.html:41 -#: templates/tough/faq.html:39 -msgid "" -"If on the other hand someone gives a misleading answer, the answer will be " -"voted down and he/she loses some points." -msgstr "" - -#: templates/faq.html:42 templates/backup/faq.html:42 -#: templates/tough/faq.html:40 -msgid "" -"Each vote in favor will generate 10 points, each vote " -"against will subtract 2 points." -msgstr "" - -#: templates/faq.html:43 templates/backup/faq.html:43 -#: templates/tough/faq.html:41 -msgid "" -"Through the votes of other people you can accumulate a maximum of " -"200 points." -msgstr "" - -#: templates/faq.html:44 templates/backup/faq.html:44 -msgid "After accumulating certain number of points, you can do more:" -msgstr "" - -#: templates/faq.html:52 templates/user_votes.html:14 -#: templates/backup/faq.html:52 templates/backup/user_votes.html:13 -#: templates/tough/faq.html:50 +#: templates/faq.html:59 templates/user_votes.html:14 msgid "upvote" msgstr "" -#: templates/faq.html:56 templates/backup/faq.html:56 -#: templates/tough/faq.html:54 +#: templates/faq.html:63 msgid "use tags" msgstr "" -#: templates/faq.html:60 templates/backup/faq.html:60 -#: templates/tough/faq.html:58 +#: templates/faq.html:68 msgid "add comments" msgstr "" -#: templates/faq.html:64 templates/user_votes.html:16 -#: templates/backup/faq.html:64 templates/backup/user_votes.html:15 -#: templates/tough/faq.html:62 +#: templates/faq.html:72 templates/user_votes.html:16 msgid "downvote" msgstr "" -#: templates/faq.html:71 templates/backup/faq.html:71 -#: templates/tough/faq.html:69 +#: templates/faq.html:75 +msgid "open and close own questions" +msgstr "" + +#: templates/faq.html:79 msgid "retag questions" msgstr "" -#: templates/faq.html:75 templates/backup/faq.html:75 -#: templates/tough/faq.html:73 +#: templates/faq.html:83 msgid "edit community wiki questions" msgstr "" -#: templates/faq.html:79 templates/backup/faq.html:79 -#: templates/tough/faq.html:77 +#: templates/faq.html:87 msgid "edit any answer" msgstr "" -#: templates/faq.html:83 templates/backup/faq.html:83 -#: templates/tough/faq.html:81 +#: templates/faq.html:91 msgid "open any closed question" msgstr "" -#: templates/faq.html:87 templates/backup/faq.html:87 -#: templates/tough/faq.html:85 +#: templates/faq.html:95 msgid "delete any comment" msgstr "" -#: templates/faq.html:91 templates/backup/faq.html:91 -#: templates/tough/faq.html:89 +#: templates/faq.html:99 msgid "delete any questions and answers and perform other moderation tasks" msgstr "" -#: templates/faq.html:98 templates/backup/faq.html:98 +#: templates/faq.html:106 +msgid "how to validate email title" +msgstr "How to validate email and why?" + +#: templates/faq.html:108 +msgid "how to validate email info" +msgstr "" +"

    How? If you have just set or changed your email " +"address - check your email and click the included link." +"
    The link contains a key generated specifically for you. You can also " +"