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

+ 64 - 0
misago/templates/misago/activation/request.html

@@ -0,0 +1,64 @@
+{% extends "misago/base.html" %}
+{% load i18n %}
+
+
+{% block title %}{% trans "Request activation e-mail" %} | {{ block.super }}{% endblock %}
+
+
+{% block content %}
+<div class="container">
+
+  <div class="row">
+    <div class="col-md-4 col-md-offset-4">
+
+      <div class="form-panel">
+        <form method="POST" role="form">
+          {% csrf_token %}
+
+          <div class="form-header">
+            <h2>{% trans "Request activation e-mail" %}</h2>
+          </div>
+
+          {% for error in form.non_field_errors %}
+          <div class="form-errors-block">
+            <ul class="list-unstyled">
+              <li>
+                {{ error }}
+              </li>
+              {% if form.user_ban and form.user_ban.user_message %}
+              <li>
+                <small>
+                  {{ form.user_ban.user_message|escape|linebreaksbr|urlize }}
+                </small>
+              </li>
+              {% elif form.user_cache.activation_by_user %}
+              <li>
+                <a href="#">{% trans "Click here to activate your account." %}</a>
+              </li>
+              {% endif %}
+            </ul>
+          </div>
+          {% endfor %}
+
+          <div class="form-body no-fieldsets">
+
+            <div class="form-group">
+              <div class="control-input">
+                <input type="text" name="username" class="form-control input-lg" placeholder="{% trans "Username or e-mail" %}" {% if form.username.value %}value="{{ form.username.value }}"{% endif %}>
+              </div>
+            </div>
+
+          </div>
+
+          <div class="form-footer">
+            <button class="btn btn-primary btn-block">{% trans "Send e-mail" %}</button>
+          </div>
+
+        </form>
+      </div>
+
+    </div>
+  </div>
+
+</div>
+{% endblock content %}

+ 29 - 0
misago/templates/misago/activation/sent.html

@@ -0,0 +1,29 @@
+{% extends "misago/base.html" %}
+{% load i18n %}
+
+
+{% block title %}{% trans "Activation sent" %} | {{ block.super }}{% endblock %}
+
+
+{% block content %}
+<div class="page-header">
+  <div class="container">
+    <h1>
+      <span class="fa fa-check">
+      {% trans "Activation sent" %}
+    </h1>
+  </div>
+</div>
+
+<div class="container">
+  <div class="misago-markup">
+
+    <p class="lead">
+      {% blocktrans trimmed with email=requesting_user.email %}
+      We have sent an e-mail to {{ email }} with a link that you have to click to activate your account.
+      {% endblocktrans %}
+    </p>
+
+  </div>
+</div>
+{% endblock content %}

+ 23 - 0
misago/templates/misago/auth_form_errors.html

@@ -0,0 +1,23 @@
+{% load i18n %}
+{% for error in form.non_field_errors %}
+<div class="form-errors-block">
+  <ul class="list-unstyled">
+    <li>
+      {{ error }}
+    </li>
+    {% if form.user_ban and form.user_ban.user_message %}
+    <li>
+      <small>
+        {{ form.user_ban.user_message|escape|linebreaksbr|urlize }}
+      </small>
+    </li>
+    {% elif form.user_cache.activation_by_user %}
+    <li>
+      <a href="{% url 'misago:request_activation' %}">
+        {% trans "Click here to activate your account." %}
+      </a>
+    </li>
+    {% endif %}
+  </ul>
+</div>
+{% endfor %}

+ 13 - 0
misago/templates/misago/emails/activation/by_user.html

@@ -0,0 +1,13 @@
+{% extends "misago/emails/base.html" %}
+{% load i18n misago_capture %}
+
+
+{% block content %}
+{% blocktrans trimmed with username=recipient.username %}
+{{ username }}, to activate your account click link below:
+{% endblocktrans %}
+<br>
+<br>
+<a href="{{ SITE_ADDRESS }}{% url 'misago:activate_by_token' user_id=recipient.pk token=activation_token %}">{% trans "Activate my account!" %}</a>
+<br>
+{% endblock content %}

+ 9 - 0
misago/templates/misago/emails/activation/by_user.txt

@@ -0,0 +1,9 @@
+{% extends "misago/emails/base.txt" %}
+{% load i18n %}
+
+{% block content %}
+{% blocktrans trimmed with username=recipient.username %}
+{{ username }}, to activate your account click link below:
+{% endblocktrans %}
+{{ SITE_ADDRESS }}{% url 'misago:activate_by_token' user_id=recipient.pk token=activation_token %}"
+{% endblock content %}

+ 6 - 25
misago/templates/misago/login.html

@@ -19,26 +19,7 @@
             <h1>{% trans "Sign in" %}</h1>
           </div>
 
-          {% for error in form.non_field_errors %}
-          <div class="form-errors-block">
-            <ul class="list-unstyled">
-              <li>
-                {{ error }}
-              </li>
-              {% if form.user_ban and form.user_ban.user_message %}
-              <li>
-                <small>
-                  {{ form.user_ban.user_message|escape|linebreaksbr|urlize }}
-                </small>
-              </li>
-              {% elif form.user_cache.activation_by_user %}
-              <li>
-                <a href="#">{% trans "Click here to activate your account." %}</a>
-              </li>
-              {% endif %}
-            </ul>
-          </div>
-          {% endfor %}
+          {% include "misago/auth_form_errors.html" %}
 
           <div class="form-body no-fieldsets">
 
@@ -66,13 +47,13 @@
     <div class="col-md-4">
 
       <h4>
-        <span class="fa fa-question-circle"></span>
+        <span class="fa fa-fw fa-question-circle"></span>
         {% trans "Solving problems:" %}
       </h4>
       <ul class="list-unstyled signin-help">
         <li>
           <h5>
-            <span class="fa fa-key"></span>
+            <span class="fa fa-fw fa-key"></span>
             {% trans "I don't remember my password." %}
           </h5>
           <a href="#">
@@ -81,16 +62,16 @@
         </li>
         <li>
           <h5>
-            <span class="fa fa-power-off"></span>
+            <span class="fa fa-fw fa-power-off"></span>
             {% trans "My account is inactive." %}
           </h5>
-          <a href="#">
+          <a href="{% url 'misago:request_activation' %}">
             {% trans "Request activation e-mail" %}
           </a>
         </li>
         <li>
           <h5>
-            <span class="fa fa-pencil-square-o"></span>
+            <span class="fa fa-fw fa-pencil-square-o"></span>
             {% trans "I don't have an account." %}
           </h5>
           <a href="{% url 'misago:register' %}">

+ 84 - 35
misago/users/forms/auth.py

@@ -1,5 +1,5 @@
 from django.core.exceptions import ValidationError
-from django.contrib.auth import authenticate
+from django.contrib.auth import authenticate, get_user_model
 from django.contrib.auth.forms import (AuthenticationForm as
                                        BaseAuthenticationForm)
 from django.template.defaultfilters import date as format_date
@@ -9,7 +9,45 @@ from misago.core import forms
 from misago.users.bans import get_user_ban
 
 
-class AuthenticationForm(forms.Form, BaseAuthenticationForm):
+class MisagoAuthMixin(object):
+    def confirm_user_active(self, user):
+        if user.requires_activation_by_admin:
+            raise ValidationError(
+                self.error_messages['inactive_admin'],
+                code='inactive_admin',
+            )
+
+        if user.requires_activation_by_user:
+            raise ValidationError(
+                self.error_messages['inactive_user'],
+                code='inactive_user',
+            )
+
+    def confirm_user_not_banned(self, user):
+        self.user_ban = get_user_ban(user)
+        if self.user_ban:
+            if self.user_ban.valid_until:
+                if self.user_ban.user_message:
+                    message = _("%(username)s, your account is "
+                                "banned until %(date)s for:")
+                else:
+                    message = _("%(username)s, your account "
+                                "is banned until %(date)s.")
+                date_format = {'date': format_date(self.user_ban.valid_until)}
+                message = message % date_format
+            else:
+                if self.user_ban.user_message:
+                    message = _("%(username)s, your account is banned for:")
+                else:
+                    message = _("%(username)s, your account is banned.")
+
+            raise ValidationError(
+                message % {'username': self.user_cache.username},
+                code='banned',
+            )
+
+
+class AuthenticationForm(MisagoAuthMixin, forms.Form, BaseAuthenticationForm):
     """
     Base class for authenticating users, Floppy-forms and
     Misago login field comliant
@@ -52,39 +90,8 @@ class AuthenticationForm(forms.Form, BaseAuthenticationForm):
         return self.cleaned_data
 
     def confirm_login_allowed(self, user):
-        if user.requires_activation_by_admin:
-            raise ValidationError(
-                self.error_messages['inactive_admin'],
-                code='inactive_admin',
-            )
-
-        if user.requires_activation_by_user:
-            raise ValidationError(
-                self.error_messages['inactive_user'],
-                code='inactive_user',
-            )
-
-        self.user_ban = get_user_ban(user)
-        if self.user_ban:
-            if self.user_ban.valid_until:
-                if self.user_ban.user_message:
-                    message = _("%(username)s, your account is "
-                                "banned until %(date)s for:")
-                else:
-                    message = _("%(username)s, your account "
-                                "is banned until %(date)s.")
-                date_format = {'date': format_date(self.user_ban.valid_until)}
-                message = message % date_format
-            else:
-                if self.user_ban.user_message:
-                    message = _("%(username)s, your account is banned for:")
-                else:
-                    message = _("%(username)s, your account is banned.")
-
-            raise ValidationError(
-                message % {'username': self.user_cache.username},
-                code='banned',
-            )
+        self.confirm_user_active(user)
+        self.confirm_user_not_banned(user)
 
 
 class AdminAuthenticationForm(AuthenticationForm):
@@ -103,3 +110,45 @@ class AdminAuthenticationForm(AuthenticationForm):
                 self.error_messages['not_staff'],
                 code='not_staff',
             )
+
+
+class GetUserForm(MisagoAuthMixin, forms.Form):
+    username = forms.CharField(label=_("Username or e-mail"))
+
+    def clean(self):
+        data = super(GetUserForm, self).clean()
+
+        credential = data.get('username')
+        if not credential or len(credential) > 250:
+            raise forms.ValidationError(_("You have to fill out form."))
+
+        try:
+            User = get_user_model()
+            user =  User.objects.get_by_username_or_email(data['username'])
+            self.user_cache = user
+        except User.DoesNotExist:
+            raise forms.ValidationError(_("User could not be found."))
+
+        self.confirm_allowed(user)
+
+        return data
+
+    def confirm_allowed(self, user):
+        raise NotImplementedError("confirm_allowed method must be defined "
+                                  "by inheriting classes")
+
+
+class ResendActivationForm(GetUserForm):
+    def confirm_allowed(self, user):
+        self.confirm_user_not_banned(user)
+
+        username_format = {'username': user.username}
+
+        if not user.requires_activation:
+            message = _("%(username)s, your account is already active.")
+            raise forms.ValidationError(message % username_format)
+
+        if user.requires_activation_by_admin:
+            message = _("%(username)s, only administrator may activate "
+                        "your account.")
+            raise forms.ValidationError(message % username_format)

+ 57 - 0
misago/users/tests/test_activation_views.py

@@ -0,0 +1,57 @@
+from django.contrib.auth import get_user_model
+from django.core import mail
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+
+from misago.users.models import Ban, BAN_USERNAME
+
+
+class ActivationViewTests(TestCase):
+    def test_view_get_returns_200(self):
+        """request activation view returns 200 on GET"""
+        response = self.client.get(reverse('misago:request_activation'))
+        self.assertEqual(response.status_code, 200)
+
+    def test_view_submit(self):
+        """request activation view sends mail"""
+        User = get_user_model()
+        User.objects.create_user('Bob', 'bob@test.com', 'Pass.123',
+                                 requires_activation=1)
+
+        response = self.client.post(
+            reverse('misago:request_activation'),
+            data={'username': 'Bob'})
+
+        self.assertEqual(response.status_code, 302)
+
+        self.assertIn('Account activation', mail.outbox[0].subject)
+
+    def test_view_submit_banned(self):
+        """request activation for banned shows error"""
+        User = get_user_model()
+        User.objects.create_user('Bob', 'bob@test.com', 'Pass.123',
+                                 requires_activation=1)
+        Ban.objects.create(test=BAN_USERNAME, banned_value='bob',
+                           user_message='Nope!')
+
+        response = self.client.post(
+            reverse('misago:request_activation'),
+            data={'username': 'Bob'})
+        self.assertEqual(response.status_code, 200)
+        self.assertIn('Nope!', response.content)
+
+
+        self.assertTrue(not mail.outbox)
+
+    def test_view_submit_active(self):
+        """request activation for active shows error"""
+        User = get_user_model()
+        User.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
+
+        response = self.client.post(
+            reverse('misago:request_activation'),
+            data={'username': 'Bob'})
+        self.assertEqual(response.status_code, 200)
+        self.assertIn('already active', response.content)
+
+        self.assertTrue(not mail.outbox)

+ 7 - 0
misago/users/tests/test_registration_views.py

@@ -1,4 +1,5 @@
 from django.contrib.auth import get_user_model
+from django.core import mail
 from django.core.urlresolvers import reverse
 from django.test import TestCase
 
@@ -51,6 +52,8 @@ class RegisterViewTests(TestCase):
         response = self.client.get(reverse('misago:index'))
         self.assertIn('Bob', response.content)
 
+        self.assertIn('Welcome', mail.outbox[0].subject)
+
     def test_register_view_post_creates_inactive_user(self):
         """register view creates inactive user on POST"""
         settings.override_setting('account_activation', 'user')
@@ -68,6 +71,8 @@ class RegisterViewTests(TestCase):
         user = User.objects.get_by_username('Bob')
         user = User.objects.get_by_email('bob@bob.com')
 
+        self.assertIn('Welcome', mail.outbox[0].subject)
+
     def test_register_view_post_creates_admin_activated_user(self):
         """register view creates admin activated user on POST"""
         settings.override_setting('account_activation', 'admin')
@@ -84,3 +89,5 @@ class RegisterViewTests(TestCase):
         User = get_user_model()
         user = User.objects.get_by_username('Bob')
         user = User.objects.get_by_email('bob@bob.com')
+
+        self.assertIn('Welcome', mail.outbox[0].subject)

+ 2 - 0
misago/users/urls.py

@@ -14,6 +14,8 @@ urlpatterns += patterns('misago.users.views.register',
 
 
 urlpatterns += patterns('misago.users.views.activation',
+    url(r'^activation/request/$', 'request_activation', name="request_activation"),
+    url(r'^activation/sent/$', 'activation_sent', name="activation_sent"),
     url(r'^activation/(?P<user_id>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'activate_by_token', name="activate_by_token"),
 )
 

+ 50 - 0
misago/users/views/activation.py

@@ -5,12 +5,62 @@ from django.shortcuts import get_object_or_404, redirect, render
 from django.utils.translation import ugettext as _
 
 from misago.conf import settings
+from misago.core.mail import mail_user
+
 from misago.users.decorators import deny_authenticated, deny_banned_ips
+from misago.users.forms.auth import ResendActivationForm
 from misago.users.models import ACTIVATION_REQUIRED_NONE
 from misago.users.tokens import (make_activation_token,
                                  is_activation_token_valid)
 
 
+@deny_authenticated
+@deny_banned_ips
+def request_activation(request):
+    form = ResendActivationForm()
+
+    if request.method == 'POST':
+        form = ResendActivationForm(request.POST)
+        if form.is_valid():
+            requesting_user = form.user_cache
+            request.session['activation_sent_to'] = requesting_user.pk
+
+            activation_token = make_activation_token(requesting_user)
+
+            activation_by_admin = requesting_user.requires_activation_by_admin
+            activation_by_user = requesting_user.requires_activation_by_user
+
+            mail_subject = _("Account activation on %(forum_title)s forums")
+            mail_subject = mail_subject % {'forum_title': settings.forum_name}
+
+            mail_user(
+                request, requesting_user, mail_subject,
+                'misago/emails/activation/by_user',
+                {
+                    'activation_token': activation_token,
+                })
+
+            return redirect('misago:activation_sent')
+
+    return render(request, 'misago/activation/request.html',
+                  {'form': form})
+
+
+
+@deny_authenticated
+@deny_banned_ips
+def activation_sent(request):
+    requesting_user_pk = request.session.get('activation_sent_to')
+    if not requesting_user_pk:
+        raise Http404()
+
+    User = get_user_model()
+    requesting_user = get_object_or_404(User.objects, pk=requesting_user_pk)
+
+    return render(request, 'misago/activation/sent.html',
+                  {'requesting_user': requesting_user})
+
+
 class ActivationStopped(Exception):
     pass
 

+ 1 - 1
misago/users/views/register.py

@@ -102,7 +102,7 @@ def register_completed(request):
     """
     registered_user_pk = request.session.get('registered_user')
     if not registered_user_pk:
-        raise Http404
+        raise Http404()
 
     registered_user = get_object_or_404(get_user_model().objects,
                                         pk=registered_user_pk)