def process_action(self):
hash = ValidationHash.objects.create_new(self.user, 'email', [self.user.email])
- send_template_email([self.user], "auth/email_validation.html", {'validation_code': hash})
+ send_template_email([self.user], "auth/welcome_email.html", {'validation_code': hash})
def describe(self, viewer=None):
return _("%(user)s %(have_has)s joined the %(app_name)s Q&A community") % {
trigger = isinstance(action, Action) and action or None
if not awarded:
- AwardAction(user=user, node=node, ip=action.ip).save(data=dict(badge=cls.ondb, trigger=trigger))
+ AwardAction(user=user, node=node).save(data=dict(badge=cls.ondb, trigger=trigger))
except MultipleObjectsReturned:
if node:
logging.error('Found multiple %s badges awarded for user %s (%s)' % (self.name, user.username, user.id))
return decorated
+def false_if_validation_required_to(item):
+ def decorator(fn):
+ def decorated(self, *args, **kwargs):
+ if item in settings.REQUIRE_EMAIL_VALIDATION_TO and not self.email_isvalid:
+ return False
+ else:
+ return fn(self, *args, **kwargs)
+ return decorated
+ return decorator
+
class User(BaseModel, DjangoUser):
is_approved = models.BooleanField(default=False)
email_isvalid = models.BooleanField(default=False)
@property
def is_siteowner(self):
- #temporary thing, for now lets just assume that the site owner will always be the first user of the application
+ #todo: temporary thing, for now lets just assume that the site owner will always be the first user of the application
return self.id == 1
@property
def can_vote_down(self):
return self.reputation >= int(settings.REP_TO_VOTE_DOWN)
+ @false_if_validation_required_to('flag')
def can_flag_offensive(self, post=None):
if post is not None and post.author == self:
return False
return self.reputation >= int(settings.REP_TO_VIEW_FLAGS)
@true_if_is_super_or_staff
+ @false_if_validation_required_to('comment')
def can_comment(self, post):
return self == post.author or self.reputation >= int(settings.REP_TO_COMMENT
- ) or (post.__class__.__name__ == "Answer" and self == post.question.author)
+ ) or (post.__class__.__name__ == "Answer" and self == post.question.author)
@true_if_is_super_or_staff
def can_like_comment(self, comment):
def can_upload_files(self):
return self.reputation >= int(settings.REP_TO_UPLOAD)
+ def email_valid_and_can_ask(self):
+ return 'ask' not in settings.REQUIRE_EMAIL_VALIDATION_TO or self.email_isvalid
+
+ def email_valid_and_can_answer(self):
+ return 'answer' not in settings.REQUIRE_EMAIL_VALIDATION_TO or self.email_isvalid
+
def check_password(self, old_passwd):
self.__dict__.update(self.__class__.objects.filter(id=self.id).values('password')[0])
return DjangoUser.check_password(self, old_passwd)
from forms import CommaStringListWidget
+from django.forms import CheckboxSelectMultiple
from base import Setting, SettingSet
from django.utils.translation import ugettext as _
label = _("Force unique email"),
help_text = _("Should each user have an unique email.")))
+REQUIRE_EMAIL_VALIDATION_TO = Setting('REQUIRE_EMAIL_VALIDATION_TO', [], USERS_SET, dict(
+label = _("Require email validation to..."),
+help_text = _("Which actions in this site, users without a valid email will be prevented from doing."),
+widget=CheckboxSelectMultiple,
+choices=(("ask", _("ask questions")), ("answer", _("provide answers")), ("comment", _("make comments")), ("flag", _("report posts"))),
+required=False,
+))
+
+HOLD_PENDING_POSTS_MINUTES = Setting('HOLD_PENDING_POSTS_MINUTES', 120, USERS_SET, dict(
+label=_("Hold pending posts for X minutes"),
+help_text=_("How much time in minutes a post should be kept in session until the user logs in or validates the email.")
+))
+
+WARN_PENDING_POSTS_MINUTES = Setting('WARN_PENDING_POSTS_MINUTES', 15, USERS_SET, dict(
+label=_("Warn about pending posts afer X minutes"),
+help_text=_("How much time in minutes a user that just logged in or validated his email should be warned about a pending post instead of publishing it automatically.")
+))
\ No newline at end of file
<form id="fmask" action="" method="post" accept-charset="utf-8">
{% if not request.user.is_authenticated %}
<div class="message">
- <p>{% trans "login to post question info" %}</p>
+ <span class="strong big">{% trans "You are welcome to start submitting your question anonymously." %}</span>
+ <p>{% blocktrans %}
+ After submiting your question, you will be redirected to the login/signup page.
+ Your question will be saved in the current session and will be published after you login with your existing account,
+ or signup for a new account{% endblocktrans %}{% if "ask" in settings.REQUIRE_EMAIL_VALIDATION_TO %}
+ {% trans "and validate your email." %}{% else %}.{% endif %}</p>
</div>
{% else %}
- {% ifequal settings.EMAIL_VALIDATION 'on' %}
- {% if not request.user.email_isvalid %}
- <div class="message">
- {% blocktrans with request.user.email as email %}must have valid {{email}} to post,
- see {{email_validation_faq_url}}
- {% endblocktrans %}
- </div>
- {% endif %}
- {% endifequal %}
+ {% if not request.user.email_valid_and_can_ask %}
+ <div class="message">
+ {% blocktrans %}Remember, your question will not be published until you validate your email.{% endblocktrans %}
+ <a href="{% url send_validation_email %}">{% trans "Send me a validation link." %}</a>
+ </div>
+ {% endif %}
{% endif %}
<div class="form-item">
<label for="id_title" ><strong>{{ form.title.label_tag }}:</strong></label> <span class="form-error"></span><br/>
--- /dev/null
+{% load i18n extra_tags email_tags %}
+
+{% declare %}
+ prefix = settings.EMAIL_SUBJECT_PREFIX
+ app_name = settings.APP_SHORT_NAME
+
+ exclude_finetune = True
+{% enddeclare %}
+
+{% email %}
+ {% subject %}{% blocktrans %}{{ prefix }} Your email validation link {{ app_name }}{% endblocktrans %}{% endsubject %}
+
+ {% htmlcontent notifications/base.html %}
+ <p style="{{ p_style }}}">{% trans "Please use the following link to help us verify your email address:" %}</p>
+
+ <a style="{{ a_style }}}" href="{% fullurl auth_validate_email user=recipient.id,code=validation_code %}">{% trans "Validate my email address" %}</a>
+
+ <p style="{{ p_style }}}">{% trans "If the above link is not clickable, copy and paste this url into your web browser's address bar:" %}</p>
+
+ <p style="{{ p_style }}">{% fullurl auth_validate_email user=recipient.id,code=validation_code %}</p>
+ {% endhtmlcontent %}
+
+{% textcontent notifications/base_text.html %}
+{% trans "Copy and paste this url into your web browser's address bar to help us verify your email address:" %}
+
+{% fullurl auth_validate_email user=recipient.id,code=validation_code %}
+{% endtextcontent %}
+
+{% endemail %}
+
{% endspaceless %}\r
</div>\r
{% if not request.user.is_authenticated %}\r
- <div class="message">{% trans "you can answer anonymously and then login" %}</div>\r
+ <div class="message">{% trans "You can answer anonymously and then login." %}</div>\r
{% else %}\r
<p class="message">\r
{% ifequal request.user question.author %}\r
- {% trans "answer your own question only to give an answer" %}\r
+ {% trans "Answer your own question only to give an answer." %}\r
{% else %}\r
- {% trans "please only give an answer, no discussions" %}\r
+ {% trans "Please only give an answer, no discussions." %}\r
{% endifequal %}\r
+ {% if not request.user.email_valid_and_can_answer %}\r
+ {% blocktrans %}Remember, your answer will not be published until you validate your email.{% endblocktrans %}\r
+ <a href="{% url send_validation_email %}">{% trans "Send me a validation link." %}</a>\r
+ {% endif %}\r
</p>\r
{% endif %}\r
\r
{% endif %}
</tr>
{% endif %}
- {% if request.user.is_superuser %}
+ {% if can_view_private %}
<tr>
<td>{% trans "email" %}</td>
- <td><a href="mailto: {{ view_user.email }}">{{ view_user.email }}</a></td>
+ <td>
+ <a href="mailto: {{ view_user.email }}">{{ view_user.email }}</a>
+ {% if not view_user.email_isvalid %}
+ ({% trans "not validated" %})
+ {% ifequal request.user view_user %}
+ </td></tr><tr><td></td><td><a href="{% url send_validation_email %}">{% trans "Send me a validation link." %}</a>
+ {% endifequal %}
+ {% endif %}
+ </td>
</tr>
{% endif %}
<!--
kwargs=dict(close=True), name='close'),
url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.commands.close,
kwargs=dict(close=False), name='reopen'),
- url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.writers.answer, name='answer'),
+ url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.writers.answer, name='answer'),
+ url(r'^%s(?P<action>\w+)/$' % _('pending-data/'), app.writers.manage_pending_data, name='manage_pending_data'),
url(r'^%s(?P<id>\d+)/(?P<vote_type>[a-z]+)/' % _('vote/'), app.commands.vote_post,
name='vote_post'),
url(r'^%s%s$' % (_('account/'), _('signin/')), app.auth.signin_page, name='auth_signin'),
url(r'^%s%s$' % (_('account/'), _('signout/')), app.auth.signout, name='user_signout'),
- url(r'^%s%s(?P<action>\w+)/$' % (_('account/'), _('signin/')), app.auth.signin_page,
- name='auth_action_signin'),
url(r'^%s(?P<provider>\w+)/%s$' % (_('account/'), _('signin/')),
app.auth.prepare_provider_signin, name='auth_provider_signin'),
url(r'^%s(?P<provider>\w+)/%s$' % (_('account/'), _('done/')), app.auth.process_provider_signin,
app.auth.remove_external_provider, name='user_remove_external_provider'),
url(r'^%s%s%s$' % (_('account/'), _('providers/'), _('add/')), app.auth.signin_page,
name='user_add_external_provider'),
+ url(r'^%s%s$' %(_('account/'), _('send-validation/')), app.auth.send_validation_email, name="send_validation_email"),
url(r'^%s$' % _('admin/'), app.admin.dashboard, name="admin_index"),
from django.contrib.auth import login, logout
from django.http import get_host
from forum.actions import SuspendAction
+from forum.utils import html
+from forum import settings
+from writers import manage_pending_data
import types
import datetime
import logging
from forum.models import AuthKeyUserAssociation, ValidationHash, Question, Answer
from forum.actions import UserJoinsAction
-def signin_page(request, action=None):
- if action is None:
- request.session['on_signin_url'] = request.META.get('HTTP_REFERER', '/')
- else:
- request.session['on_signin_action'] = action
- request.session['on_signin_url'] = reverse('auth_action_signin', kwargs={'action': action})
+def signin_page(request):
+ request.session['on_signin_url'] = request.META.get('HTTP_REFERER', '/')
all_providers = [provider.context for provider in AUTH_PROVIDERS.values()]
else:
raise Http404()
+def send_validation_email(request):
+ if not request.user.is_authenticated():
+ return HttpResponseUnauthorized(request)
+ else:
+ try:
+ hash = ValidationHash.objects.get(user=request.user, type='email')
+ if hash.expiration < datetime.datetime.now():
+ hash.delete()
+ return send_validation_email(request)
+ except:
+ hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
+
+ send_template_email([request.user], "auth/mail_validation.html", {'validation_code': hash})
+ request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
+ return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
+
+
+
def validate_email(request, user, code):
user = get_object_or_404(User, id=user)
association.delete()
return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
-def newquestion_signin_action(user):
- question = Question.objects.filter(author=user).order_by('-added_at')[0]
- return question.get_absolute_url()
-
-def newanswer_signin_action(user):
- answer = Answer.objects.filter(author=user).order_by('-added_at')[0]
- return answer.get_absolute_url()
-
-POST_SIGNIN_ACTIONS = {
-'newquestion': newquestion_signin_action,
-'newanswer': newanswer_signin_action,
-}
-
def login_and_forward(request, user, forward=None, message=None):
if user.is_suspended():
return forward_suspended_user(request, user)
user.backend = "django.contrib.auth.backends.ModelBackend"
login(request, user)
- temp_data = request.session.pop('temp_node_data', None)
- if temp_data:
- request.POST = temp_data
- node_type = request.session.pop('temp_node_type')
-
- if node_type == "question":
- from forum.views.writers import ask
- return ask(request)
- elif node_type == "answer":
- from forum.views.writers import answer
- return answer(request, request.session.pop('temp_question_id'))
-
- if not forward:
- signin_action = request.session.get('on_signin_action', None)
- if not signin_action:
- forward = request.session.get('on_signin_url', None)
-
- if not forward:
- forward = reverse('index')
- else:
- try:
- forward = POST_SIGNIN_ACTIONS[signin_action](user)
- except:
- forward = reverse('index')
-
if message is None:
message = _("Welcome back %s, you are now logged in") % user.username
request.user.message_set.create(message=message)
+
+ forward = request.session.get('on_signin_url', reverse('index'))
+ pending_data = request.session.get('pending_submission_data', None)
+
+ if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
+ submission_time = pending_data['time']
+ if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
+ del request.session['pending_submission_data']
+ elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
+ user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
+ html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
+ html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
+ html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
+ ))
+ else:
+ return manage_pending_data(request, _('save'), forward)
+
return HttpResponseRedirect(forward)
def forward_suspended_user(request, user, show_private_msg=True):
}\r
}\r
\r
-def set_new_email(user, new_email, nomessage=False):\r
- if new_email != user.email:\r
- user.email = new_email\r
- user.email_isvalid = False\r
- user.save()\r
- #if settings.EMAIL_VALIDATION == 'on':\r
- # send_new_email_key(user,nomessage=nomessage)\r
\r
@login_required\r
def edit_user(request, id):\r
if form.is_valid():\r
new_email = sanitize_html(form.cleaned_data['email'])\r
\r
- set_new_email(user, new_email)\r
+ if new_email != user.email:\r
+ user.email = new_email\r
+ user.email_isvalid = False\r
\r
if settings.EDITABLE_SCREEN_NAME:\r
user.username = sanitize_html(form.cleaned_data['username'])\r
user.save()\r
EditProfileAction(user=user, ip=request.META['REMOTE_ADDR']).save()\r
\r
+ request.user.message_set.create(message=_("Profile updated."))\r
return HttpResponseRedirect(user.get_profile_url())\r
else:\r
form = EditUserForm(user)\r
from forum.forms import *
from forum.models import *
from forum.forms import get_next_url
+from forum.utils import html
def upload(request):#ajax upload file to a question or answer
return HttpResponse(result, mimetype="application/xml")
def ask(request):
- if request.POST and "text" in request.POST:
- form = AskForm(request.POST, user=request.user)
- if form.is_valid():
- if request.user.is_authenticated():
- ask_action = AskAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data)
- question = ask_action.node
+ form = None
- if settings.WIKI_ON and request.POST.get('wiki', False):
- question.nstate.wiki = ask_action
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- request.session['temp_node_data'] = request.POST
- request.session['temp_node_type'] = 'question'
- return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newquestion'}))
- elif request.method == "POST" and "go" in request.POST:
- form = AskForm({'title': request.POST['q']}, user=request.user)
- else:
+ if request.POST:
+ if request.session.pop('reviewing_pending_data', False):
+ form = AskForm(initial=request.POST, user=request.user)
+ elif "text" in request.POST:
+ form = AskForm(request.POST, user=request.user)
+ if form.is_valid():
+ if request.user.is_authenticated() and request.user.email_valid_and_can_ask():
+ ask_action = AskAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data)
+ question = ask_action.node
+
+ if settings.WIKI_ON and request.POST.get('wiki', False):
+ question.nstate.wiki = ask_action
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ request.session['pending_submission_data'] = {
+ 'POST': request.POST,
+ 'data_name': _("question"),
+ 'type': 'ask',
+ 'submission_url': reverse('ask'),
+ 'time': datetime.datetime.now()
+ }
+
+ if request.user.is_authenticated():
+ request.user.message_set.create(message=_("Your question is pending until you %s.") % html.hyperlink(
+ reverse('send_validation_email'), _("validate your email")
+ ))
+ return HttpResponseRedirect(reverse('index'))
+ else:
+ return HttpResponseRedirect(reverse('auth_signin'))
+ elif "go" in request.POST:
+ form = AskForm({'title': request.POST['q']}, user=request.user)
+
+ if not form:
form = AskForm(user=request.user)
return render_to_response('ask.html', {
def answer(request, id):
question = get_object_or_404(Question, id=id)
+
if request.POST:
form = AnswerForm(question, request.POST)
- if form.is_valid():
- if request.user.is_authenticated():
- answer_action = AnswerAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(dict(question=question, **form.cleaned_data))
- answer = answer_action.node
- if settings.WIKI_ON and request.POST.get('wiki', False):
- answer.nstate.wiki = answer_action
-
- return HttpResponseRedirect(answer.get_absolute_url())
- else:
- request.session['temp_node_data'] = request.POST
- request.session['temp_node_type'] = 'answer'
- request.session['temp_question_id'] = id
- return HttpResponseRedirect(reverse('auth_action_signin', kwargs={'action': 'newquestion'}))
- else:
+ if request.session.pop('reviewing_pending_data', False) or not form.is_valid():
request.session['redirect_POST_data'] = request.POST
return HttpResponseRedirect(question.get_absolute_url() + '#fmanswer')
+ if request.user.is_authenticated() and request.user.email_valid_and_can_answer():
+ answer_action = AnswerAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(dict(question=question, **form.cleaned_data))
+ answer = answer_action.node
+
+ if settings.WIKI_ON and request.POST.get('wiki', False):
+ answer.nstate.wiki = answer_action
+
+ return HttpResponseRedirect(answer.get_absolute_url())
+ else:
+ request.session['pending_submission_data'] = {
+ 'POST': request.POST,
+ 'data_name': _("answer"),
+ 'type': 'answer',
+ 'submission_url': reverse('answer', kwargs={'id': id}),
+ 'time': datetime.datetime.now()
+ }
+
+ if request.user.is_authenticated():
+ request.user.message_set.create(message=_("Your answer is pending until you %s.") % html.hyperlink(
+ reverse('send_validation_email'), _("validate your email")
+ ))
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ return HttpResponseRedirect(reverse('auth_signin'))
+
return HttpResponseRedirect(question.get_absolute_url())
+
+def manage_pending_data(request, action, forward=None):
+ pending_data = request.session.pop('pending_submission_data', None)
+
+ if not pending_data:
+ raise Http404
+
+ if action == _("cancel"):
+ return HttpResponseRedirect(forward or request.META.get('HTTP_REFERER', '/'))
+ else:
+ if action == _("review"):
+ request.session['reviewing_pending_data'] = True
+
+ request.session['redirect_POST_data'] = pending_data['POST']
+ return HttpResponseRedirect(pending_data['submission_url'])
+
+
USE_I18N = True
LANGUAGE_CODE = 'en'
-EMAIL_VALIDATION = 'off' #string - on|off
-
DJANGO_VERSION = 1.1
OSQA_DEFAULT_SKIN = 'default'