Просмотр исходного кода

WIP registration: captcha, welcome mail for active

Rafał Pitoń 11 лет назад
Родитель
Сommit
b97aabbbce

+ 62 - 19
misago/core/captcha.py

@@ -1,32 +1,67 @@
+from recaptcha.client.captcha import displayhtml, submit as submit_recaptcha
 from django.utils.translation import ugettext_lazy as _
+
 from misago.conf import settings
 from misago.core import forms
 
 
 def add_captcha_to_form(FormType, request):
-    if session_already_passed_test(request.session):
-        return FormType
-    else:
-        captcha_attrs = {}
+    captcha_type = settings.captcha_on_registration
+    test_passed = session_already_passed_test(request.session)
 
-        captcha_type = getattr(settings, FormType.captcha_setting)
-        if captcha_type == 'recaptcha':
-            captcha_attrs['has_recaptcha'] = True
-            captcha_attrs.update(add_recaptcha_to_form(request))
-        elif captcha_type == 'qa':
-            captcha_attrs['has_qa_captcha'] = True
-            captcha_attrs.update(add_qa_test_to_form(request))
+    captcha_attrs = {}
+    if captcha_type == 'recaptcha':
+        captcha_attrs.update(add_recaptcha_to_form(request, test_passed))
+    elif captcha_type == 'qa':
+        captcha_attrs.update(add_qa_test_to_form(request, test_passed))
 
-        if captcha_attrs:
-            captcha_attrs['session'] = request.session
-        return type('FinalRegisterForm', (FormType,), captcha_attrs)
+    if captcha_attrs:
+        captcha_attrs['session'] = request.session
+
+    return type('FinalRegisterForm', (FormType,), captcha_attrs)
 
 
 """
 reCaptcha
 """
-def add_recaptcha_to_form(request):
-    extra_fields = {}
+def clean_recaptcha(self):
+    if not self.data.get('recaptcha_response_field'):
+        raise forms.ValidationError(_("This field is required."))
+
+    api_response = submit_recaptcha(
+        self.data.get('recaptcha_challenge_field'),
+        self.data.get('recaptcha_response_field'),
+        settings.recaptcha_private_api_key,
+        self._misago_real_ip)
+
+    if api_response.is_valid:
+        self.has_recaptcha = False
+        mark_session_as_passing(self.session)
+    else:
+        raise forms.ValidationError(_("Text from image is incorrect."))
+
+    return ''
+
+
+def add_recaptcha_to_form(request, test_passed):
+    recaptcha_field = forms.CharField(label=_('Security image'),
+                                      required=False)
+    field_html = displayhtml(settings.recaptcha_public_api_key,
+                             request.is_secure())
+
+    extra_fields = {
+        'passed_recaptcha': test_passed,
+        'has_recaptcha': True,
+        'recaptcha': recaptcha_field,
+        'recaptcha_html': field_html,
+        '_misago_real_ip': request._misago_real_ip,
+        'clean_recaptcha': clean_recaptcha,
+    }
+
+    if test_passed:
+        extra_fields['has_recaptcha'] = False
+        extra_fields.pop('clean_recaptcha')
+
     return extra_fields
 
 
@@ -43,17 +78,25 @@ def clean_qa_answer(self):
             mark_session_as_passing(self.session)
             return self.cleaned_data['qa_answer']
     else:
-        raise forms.ValidationError(_("Entered answer is invalid."))
+        raise forms.ValidationError(_("Entered answer is incorrect."))
 
 
-def add_qa_test_to_form(request):
+def add_qa_test_to_form(request, test_passed):
     qa_answer_field = forms.CharField(label=settings.qa_question,
-                                      help_text=settings.qa_help_text)
+                                      help_text=settings.qa_help_text,
+                                      required=(not test_passed))
 
     extra_fields = {
+        'passed_qa_captcha': test_passed,
+        'has_qa_captcha': True,
         'qa_answer': qa_answer_field,
         'clean_qa_answer': clean_qa_answer,
     }
+
+    if test_passed:
+        extra_fields['has_qa_captcha'] = False
+        extra_fields.pop('clean_qa_answer')
+
     return extra_fields
 
 

+ 3 - 0
misago/core/context_processors.py

@@ -1,9 +1,12 @@
 def site_address(request):
     if request.is_secure():
+        site_protocol = 'https'
         address_template = 'https://%s'
     else:
+        site_protocol = 'http'
         address_template = 'http://%s'
     return {
+        'SITE_PROTOCOL': site_protocol,
         'SITE_HOST': request.get_host(),
         'SITE_ADDRESS': address_template % request.get_host()
     }

+ 1 - 0
misago/core/mail.py

@@ -7,6 +7,7 @@ def build_mail(request, recipient, subject, template, context=None):
     context = context or {}
     context['sender'] = request.user
     context['recipient'] = recipient
+    context['subject'] = subject
     context = RequestContext(request, context)
 
     message_plain = render_to_string('%s.txt' % template, context)

+ 2 - 2
misago/static/misago/admin/css/misago/alerts.less

@@ -13,7 +13,7 @@
     margin-bottom: 0px;
     padding: (@line-height-computed * 0.8) 0px;
 
-    font-size: @font-size-large;
+    font-size: @font-size-base;
 
     .alert-icon {
       margin: -@alert-padding 0px;
@@ -21,7 +21,7 @@
       position: relative;
       top: @font-size-large / 4;
 
-      font-size: @font-size-large * 1.8;
+      font-size: @font-size-base * 1.8;
     }
   }
 }

+ 6 - 31
misago/static/misago/css/misago/alerts.less

@@ -19,6 +19,7 @@
 
   &:active, &:focus {
     background-color: darken(@bg, 15%);
+    box-shadow: 0px 0px 0px 2px darken(@bg, 15%);
     outline: none;
 
     color: @text;
@@ -31,9 +32,10 @@
 
   .alert {
     border: none;
-    display: inline-block;
-    margin-left: auto;
-    margin-right: auto;
+    border-bottom: 6px solid fadeOut(#000, 85%);
+    border-radius: 0;
+    margin-bottom: 0px;
+    padding: (@line-height-computed * 0.8) 0px;
 
     .alert-icon {
       margin: -@alert-padding 0px;
@@ -41,7 +43,7 @@
       position: relative;
       top: @font-size-large / 4;
 
-      font-size: @font-size-large * 1.5;
+      font-size: @font-size-base * 1.8;
     }
 
     .close {
@@ -78,31 +80,4 @@
       }
     }
   }
-
-  &.alerts-fixed {
-    position: fixed;
-    top: 0;
-    z-index: @zindex-modal + 1;
-    overflow: visible;
-    width: 100%;
-
-    &>div {
-      width: 100%;
-      overflow: hidden;
-
-      .alert {
-        box-shadow: 0px 0px 0px 6px fadeOut(#000, 85%);
-        border-radius: 0px;
-        display: block;
-        padding: @line-height-computed 0px;
-
-        font-size: @font-size-large;
-
-        .alert-icon {
-          position: relative;
-          top: (@font-size-large - @font-size-base) / 2;
-        }
-      }
-    }
-  }
 }

+ 2 - 2
misago/templates/misago/emails/base.html

@@ -56,12 +56,12 @@
               <table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0">
                 <tr>
                   <td valign="middle" style="font-size: 28px; line-height: 24px; color: #555555;">{{ misago_settings.forum_name }}</td>
-                  <td align="center" valign="middle" width="30"><img src="{{ recipient|avatar:32 }}" width="32" height="32" style="border-radius: 3px;" alt=""></td>
+                  <td align="center" valign="middle" width="30"><img src="{{ SITE_PROTOCOL }}:{{ recipient|avatar:32 }}" width="32" height="32" style="border-radius: 3px;" alt=""></td>
                 </tr>
               </table>
             <br>
 
-            <div style="font-weight: bold; font-size: 18px; line-height: 24px; color: #333; border-top: 1px solid #ddd;"><br>{% block title %}{% endblock %}</div>
+            <div style="font-weight: bold; font-size: 18px; line-height: 24px; color: #333; border-top: 1px solid #ddd;"><br>{% block title %}{{ subject }}{% endblock %}</div>
             <br>
 
             {% block content %}{% endblock content %}

+ 1 - 1
misago/templates/misago/emails/base.txt

@@ -1,7 +1,7 @@
 {{ misago_settings.forum_name }}
 ================================================
 
-{% block title %}{% endblock %}
+{% block title %}{{ subject }}{% endblock %}
 
 {% block content %}{% endblock content %}
 

+ 25 - 0
misago/templates/misago/emails/register/complete.html

@@ -0,0 +1,25 @@
+{% extends "misago/emails/base.html" %}
+{% load i18n misago_capture %}
+
+
+{% block content %}
+{% blocktrans trimmed with username=recipient.username %}
+{{ username }}, thank you for joining us!
+{% endblocktrans %}
+<br>
+<br>
+{% block activation-message %}
+{% blocktrans trimmed %}
+You may now join discussion on our forums. Why not spend a minute or two to have a look around and share your opinions and knowledge with rest of community?
+{% endblocktrans %}
+<br>
+<br>
+{% capture trimmed as login_link %}
+<a href="{{ SITE_ADDRESS }}{% url LOGIN_URL %}">{% trans "this form" %}</a>
+{% endcapture %}
+{% blocktrans trimmed with login_form=login_link|safe %}
+You can always sign in to your account using {{ login_form }}.
+{% endblocktrans %}
+<br>
+{% endblock activation-message %}
+{% endblock content %}

+ 10 - 0
misago/templates/misago/emails/register/complete.txt

@@ -0,0 +1,10 @@
+{% extends "misago/emails/base.txt" %}
+
+{% block content %}
+{{ username }}, thank you for joining us!
+
+You may now join discussion on our forums. Why not spend a minute or two to have a look around and share your opinions and knowledge with rest of community?
+
+You can always sign in to your account using form below:
+{{ SITE_ADDRESS }}{% url LOGIN_URL %}
+{% endblock content %}

+ 47 - 0
misago/templates/misago/register/completed.html

@@ -0,0 +1,47 @@
+{% extends "misago/base.html" %}
+{% load i18n %}
+
+
+{% block title %}{% trans "Registration complete" %} | {{ block.super }}{% endblock %}
+
+
+{% block content %}
+<div class="page-header">
+  <div class="container">
+    <h1>
+      <span class="fa fa-check">
+      {% trans "Registration complete" %}
+    </h1>
+  </div>
+</div>
+
+<div class="container">
+  <div class="misago-markup">
+
+    {% if activation_by_user %}
+    <p class="lead">
+      {% blocktrans trimmed with username=registered_user.username %}
+      {{ username }}, your account has been created, but you have to activate it before you will be able to sign in.
+      {% endblocktrans %}
+    </p>
+    <p class="lead">
+      {% blocktrans trimmed with email=registered_user.email %}
+      We have sent an e-mail to {{ email }} with a link that you have to click to activate your account.
+      {% endblocktrans %}
+    </p>
+    {% elif activation_by_admin %}
+    <p class="lead">
+      {% blocktrans trimmed with username=registered_user.username %}
+      {{ username }}, your account has been created, but administrator has to activate it before you will be able to sign in.
+      {% endblocktrans %}
+    </p>
+    <p class="lead">
+      {% blocktrans trimmed %}
+      You will be notified with e-mail when this happens.
+      {% endblocktrans %}
+    </p>
+    {% endif %}
+
+  </div>
+</div>
+{% endblock content %}

+ 44 - 0
misago/templates/misago/register/form.html

@@ -51,6 +51,38 @@
               </div>
             </div>
 
+            {% if form.has_recaptcha %}
+            <div class="form-group {% if form.recaptcha.errors %}has-error{% endif %}">
+              <label class="control-label {{ label_class }}" for="{{ form.recaptcha.auto_id }}">{{ form.recaptcha.label }}:</label>
+              <div class="{{ field_class }}">
+                {{ form.recaptcha_html|safe }}
+                {% if form.recaptcha.errors %}
+                <div class="control-errors">
+                  {% for error in form.recaptcha.errors %}
+                  <p class="help-block">
+                    <strong>{{ error }}</strong>
+                  </p>
+                  {% endfor %}
+                </div>
+                {% endif %}
+                {% if form.recaptcha.help_text %}
+                <p class="help-block">{{ form.recaptcha.help_text }}</p>
+                {% endif %}
+              </div>
+            </div>
+            {% elif form.passed_recaptcha %}
+            <div class="form-group has-success">
+              <label class="control-label {{ label_class }}">{{ form.recaptcha.label }}:</label>
+              <div class="{{ field_class }} form-control-static text-success">
+                <p>
+                  <span class="fa fa-check"></span>
+                  <strong>{% trans "Ok!" %}</strong>
+                  {% trans "Text from image was correct!" %}
+                </p>
+              </div>
+            </div>
+            {% endif %}
+
             {% if form.has_qa_captcha %}
             <div class="form-group has-feedback {% if form.qa_answer.errors %}has-error{% endif %}">
               <label class="control-label {{ label_class }}" for="{{ form.qa_answer.auto_id }}">{{ form.qa_answer.label }}:</label>
@@ -71,7 +103,19 @@
                 {% endif %}
               </div>
             </div>
+            {% elif form.passed_qa_captcha %}
+            <div class="form-group has-success">
+              <label class="control-label {{ label_class }}">{{ form.qa_answer.label }}:</label>
+              <div class="{{ field_class }} form-control-static text-success">
+                <p>
+                  <span class="fa fa-check"></span>
+                  <strong>{% trans "Ok!" %}</strong>
+                  {% trans "Your answer was correct!" %}
+                </p>
+              </div>
+            </div>
             {% endif %}
+
             {% endwith %}
 
           </div>

+ 0 - 2
misago/users/forms/register.py

@@ -4,8 +4,6 @@ from misago.users import validators
 
 
 class RegisterForm(forms.Form):
-    captcha_setting = 'captcha_on_registration'
-
     username = forms.CharField(label=_("Username"),
                                validators=[validators.validate_username])
     email = forms.CharField(label=_("Email"),

+ 14 - 2
misago/users/models/user.py

@@ -3,6 +3,7 @@ from hashlib import md5
 from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin,
                                         UserManager as BaseUserManager,
                                         AnonymousUser as DjangoAnonymousUser)
+from django.core.mail import send_mail
 from django.db import models, transaction
 from django.utils import timezone
 from django.utils.translation import ugettext_lazy as _
@@ -17,12 +18,17 @@ from misago.users.utils import hash_email
 
 
 __all__ = [
-    'AnonymousUser', 'User', 'Online'
+    'ACTIVATION_REQUIRED_NONE', 'ACTIVATION_REQUIRED_USER',
+    'ACTIVATION_REQUIRED_ADMIN', 'PRESENCE_VISIBILITY_ALL',
+    'PRESENCE_VISIBILITY_FOLLOWED', 'PRESENCE_VISIBILITY_ALLOWED',
+    'PRESENCE_VISIBILITY_CHOICES', 'AUTO_SUBSCRIBE_NONE',
+    'AUTO_SUBSCRIBE_WATCH', 'AUTO_SUBSCRIBE_WATCH_AND_EMAIL',
+    'AUTO_SUBSCRIBE_CHOICES', 'AnonymousUser', 'User', 'Online',
 ]
 
 
 ACTIVATION_REQUIRED_NONE = 0
-ACTIVATION_REQUIRED_EMAIL = 1
+ACTIVATION_REQUIRED_USER = 1
 ACTIVATION_REQUIRED_ADMIN = 2
 
 
@@ -296,6 +302,12 @@ class User(AbstractBaseUser, PermissionsMixin):
 
         self.acl_key = md5(','.join(roles_pks)).hexdigest()[:12]
 
+    def email_user(self, subject, message, from_email=None, **kwargs):
+        """
+        Sends an email to this User.
+        """
+        send_mail(subject, message, from_email, [self.email], **kwargs)
+
 
 class Online(models.Model):
     user = models.OneToOneField(User, primary_key=True,

+ 1 - 0
misago/users/urls.py

@@ -10,6 +10,7 @@ urlpatterns = patterns('misago.users.views.auth',
 
 urlpatterns += patterns('misago.users.views.register',
     url(r'^register/$', 'register', name='register'),
+    url(r'^register/completed/$', 'registration_completed', name='register_completed'),
 )
 
 

+ 63 - 10
misago/users/views/register.py

@@ -1,14 +1,18 @@
 from django.contrib import messages
 from django.contrib.auth import authenticate, get_user_model, login
-from django.shortcuts import redirect, render
+from django.http import Http404
+from django.shortcuts import get_object_or_404, redirect, render
 from django.utils.translation import ugettext as _
 from django.views.decorators.debug import sensitive_post_parameters
 
 from misago.conf import settings
 from misago.core.captcha import add_captcha_to_form
+from misago.core.mail import mail_user
 
 from misago.users.decorators import deny_authenticated, deny_banned_ips
 from misago.users.forms.register import RegisterForm
+from misago.users.models import (ACTIVATION_REQUIRED_USER,
+                                 ACTIVATION_REQUIRED_ADMIN)
 
 
 def register_decorator(f):
@@ -31,24 +35,73 @@ def register(request):
     if request.method == 'POST':
         form = SecuredForm(request.POST)
         if form.is_valid():
+            activation_kwargs = {}
+            if settings.account_activation == 'user':
+                activation_kwargs = {
+                    'requires_activation': ACTIVATION_REQUIRED_USER
+                }
+            elif settings.account_activation == 'admin':
+                activation_kwargs = {
+                    'requires_activation': ACTIVATION_REQUIRED_ADMIN
+                }
+
             User = get_user_model()
             new_user = User.objects.create_user(form.cleaned_data['username'],
                                                 form.cleaned_data['email'],
-                                                form.cleaned_data['password'])
+                                                form.cleaned_data['password'],
+                                                **activation_kwargs)
+
+            if settings.account_activation == 'none':
+                authenticated_user = authenticate(
+                    username=new_user.email,
+                    password=form.cleaned_data['password'])
+                login(request, authenticated_user)
 
-            authenticated_user = authenticate(
-                username=new_user.email,
-                password=form.cleaned_data['password'])
-            login(request, authenticated_user)
+                welcome_message = _("Welcome aboard, %(username)s!")
+                welcome_message = welcome_message % {'username': new_user.username}
+                messages.success(request, welcome_message)
 
-            welcome_message = _("Welcome aboard, %(username)s!")
-            welcome_message = welcome_message % {'username': new_user.username}
-            messages.success(request, welcome_message)
+                subject = _("Welcome on %(forum_title)s forums!")
+                subject = subject % {'forum_title': settings.forum_name}
+                mail_user(request, new_user, subject,
+                          'misago/emails/register/complete')
 
-            return redirect('misago:index')
+                return redirect('misago:index')
+            else:
+                request.session['registered_user'] = new_user.pk
+                return redirect('misago:register_completed')
 
     return render(request, 'misago/register/form.html', {'form': form, 'testname': 'and<b>rzej'})
 
 
 def registration_disabled(request):
     return render(request, 'misago/register/disabled.html')
+
+
+def registration_completed(request):
+    """
+    If user needs to activate his account, we display him page with message
+    """
+    registered_user_pk = request.session.get('registered_user')
+    if not registered_user_pk:
+        raise Http404
+
+    registered_user = get_object_or_404(get_user_model().objects,
+                                        pk=registered_user_pk)
+
+    if not registered_user.requires_activation:
+        return redirect('misago:index')
+
+    activation_method = registered_user.requires_activation
+    activation_by_admin = activation_method == ACTIVATION_REQUIRED_ADMIN
+    activation_by_user = activation_method == ACTIVATION_REQUIRED_USER
+
+    return render(
+        request,
+        'misago/register/completed.html',
+        {
+            'activation_by_admin': activation_by_admin,
+            'activation_by_user': activation_by_user,
+            'registered_user': registered_user,
+        })
+