add mail registration to app

This commit is contained in:
Dmitry Afanasyev 2021-10-27 20:03:41 +03:00
parent 196e9ab73b
commit 1b19a007ae
12 changed files with 167 additions and 33 deletions

View File

@ -51,8 +51,15 @@ EMAIL_HOST_PASSWORD=
EMAIL_PORT= EMAIL_PORT=
EMAIL_USE_SSL= EMAIL_USE_SSL=
EMAIL_USE_TLS= EMAIL_USE_TLS=
# ===== Google Recapthca =====
GOOGLE_RECAPTCHA_SECRET_KEY=
GOOGLE_RECAPTCHA_SECRET_SITE_KEY=
``` ```
## To urls.py ## To urls.py
from server.apps.accounts import urls as accounts_urls from server.apps.accounts import urls as accounts_urls
@ -63,3 +70,7 @@ url_patterns
path('admin/login/', login_required(lambda request: redirect('accounts/login/', permanent=True), path('admin/login/', login_required(lambda request: redirect('accounts/login/', permanent=True),
redirect_field_name='admin/login/?next=')), redirect_field_name='admin/login/?next=')),
## Add Google reCaptcha
https://evileg.com/uk/post/283/

View File

@ -9,7 +9,7 @@ class CustomUserAdmin(UserAdmin):
model = CustomUser model = CustomUser
list_display = ('username', 'email', 'first_name', 'last_name', 'mobile', 'verification_code', list_display = ('username', 'email', 'first_name', 'last_name', 'mobile', 'verification_code',
'is_superuser', 'is_staff', 'is_active', ) 'is_superuser', 'is_staff', 'is_active', 'activation_token', )
list_filter = ('username', 'first_name', 'last_name', 'user_created', 'is_superuser', 'is_staff', 'is_active', ) list_filter = ('username', 'first_name', 'last_name', 'user_created', 'is_superuser', 'is_staff', 'is_active', )

View File

@ -1,9 +1,7 @@
# Generated by Django 3.2.8 on 2021-10-24 11:03 # Generated by Django 3.2.8 on 2021-10-27 15:03
import django.contrib.auth.models
import django.contrib.auth.validators import django.contrib.auth.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
@ -32,6 +30,7 @@ class Migration(migrations.Migration):
('mobile', models.CharField(blank=True, help_text='Users mobile phone', max_length=15, null=True, unique=True)), ('mobile', models.CharField(blank=True, help_text='Users mobile phone', max_length=15, null=True, unique=True)),
('verification_code', models.CharField(blank=True, help_text='Verification code for bot account', max_length=10, null=True, unique=True)), ('verification_code', models.CharField(blank=True, help_text='Verification code for bot account', max_length=10, null=True, unique=True)),
('user_created', models.DateField(auto_now_add=True, help_text='Date when user has been created', verbose_name='User created')), ('user_created', models.DateField(auto_now_add=True, help_text='Date when user has been created', verbose_name='User created')),
('activation_token', models.CharField(blank=True, help_text='Activation token for any user', max_length=20, null=True)),
('email', models.EmailField(help_text='User email', max_length=30, null=True, unique=True)), ('email', models.EmailField(help_text='User email', max_length=30, null=True, unique=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),

View File

@ -11,6 +11,8 @@ class CustomUser(AbstractUser):
help_text='Verification code for bot account') help_text='Verification code for bot account')
user_created = models.DateField(editable=False, auto_now_add=True, verbose_name='User created', user_created = models.DateField(editable=False, auto_now_add=True, verbose_name='User created',
help_text='Date when user has been created') help_text='Date when user has been created')
activation_token = models.CharField(max_length=20, null=True, blank=True,
help_text='Activation token for any user')
email = models.EmailField(max_length=30, unique=True, blank=False, null=True, help_text='User email') email = models.EmailField(max_length=30, unique=True, blank=False, null=True, help_text='User email')
USERNAME_FIELD = 'username' USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email'] REQUIRED_FIELDS = ['email']
@ -18,3 +20,46 @@ class CustomUser(AbstractUser):
def __str__(self): def __str__(self):
return self.username return self.username
# # ----- ToDO: Enable this to email verification --------
#
# from django.dispatch import receiver
# from django.conf import settings
# from .tasks import mail_send
# from server.settings.components.logging import main_logger
#
#
# def user_tokens() -> dict:
#
# tokens_dict = dict()
#
# def generate_token(token_length: int) -> str:
# from random import choice
# token_chars = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890'
# generated_token = ''
# for i in range(token_length):
# generated_token += choice(token_chars)
# return generated_token
#
# tokens_dict['activation_token'] = generate_token(token_length=20)
#
# return tokens_dict
#
#
# @receiver(models.signals.post_save, sender=CustomUser)
# def post_save_user_signal_handler(sender, instance, created, **kwargs):
#
# if created and instance.username != 'admin':
# instance.activation_token = user_tokens()['activation_token']
# instance.save()
# try:
# user = CustomUser.objects.get(username=instance.username)
# email = instance.email
# subject = 'Welcome to book bot administration'
# username = f'{instance.first_name} {instance.last_name}'
# text = f'https://{settings.DOMAIN_NAME}/accounts/complete_registration/{user.activation_token}'
#
# mail_send(to_email=email, subject=subject, username=username, text_content=text)
# except Exception as e:
# main_logger.error(f'Email not send to user {instance.username}. Reason: {e}')

View File

@ -10,7 +10,6 @@ def mail_send(to_email: str, subject: str, text_content: str = '', **kwargs) ->
username = kwargs.get('username') username = kwargs.get('username')
from_email = settings.DEFAULT_FROM_EMAIL from_email = settings.DEFAULT_FROM_EMAIL
msg_html = render_to_string('registration/message.html', msg_html = render_to_string('registration/message.html', {'username': username, 'text_content': text_content})
{'username': username})
send_mail(subject, text_content, from_email, [to_email], html_message=msg_html, fail_silently=False) send_mail(subject, '', from_email, [to_email], html_message=msg_html, fail_silently=False)

View File

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block content %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:400,700">
<title>Bootstrap Simple Login Form with Blue Background</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
<style>
body {
color: #fff;
background: #3598dc;
font-family: 'Roboto', sans-serif;
}
.info-form {
padding-top: 100px;
text-align: center;
}
</style>
<div class="info-form" >
<h3>{{message}}</h3>
</div>
{% endblock %}

View File

@ -98,7 +98,11 @@ body {
<hr> <hr>
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" name="username" placeholder="Username" required="required"> {% if form.username.value %}
<input type="text" class="form-control" name="username" value="{{ form.username.value }}">
{% else %}
<input type="text" class="form-control" name="username" placeholder="Username" required="required">
{% endif %}
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="password" class="form-control" name="password" placeholder="Password" required="required"> <input type="password" class="form-control" name="password" placeholder="Password" required="required">

View File

@ -1,6 +1,8 @@
<!doctype html> <!doctype html>
<body> <body>
<div> <div>
<p style="color: green">Добро пожаловать, {{ username }}!</p> <p style="color: green">Добро пожаловать, {{username}}!</p>
<p style="color: green">Чтобы активировать учётную запись перейдите по ссылке:
<a href="{{text_content}}">Завершить регистрацию</a></p>
</div> </div>
</body> </body>

View File

@ -93,7 +93,6 @@ body {
</style> </style>
<div class="signup-form"> <div class="signup-form">
<form method="post"> <form method="post">
<h2>Sign Up</h2>
<p>Please fill in this form to create an account!</p> <p>Please fill in this form to create an account!</p>
<hr> <hr>
{% csrf_token %} {% csrf_token %}
@ -131,9 +130,8 @@ body {
{% if form.mobile.value %} {% if form.mobile.value %}
<input type="text" class="form-control" name="mobile" value="{{ form.mobile.value }}"> <input type="text" class="form-control" name="mobile" value="{{ form.mobile.value }}">
{% else %} {% else %}
<input id="mobile" type="row" class="form-control" name="mobile" placeholder="Mobile" > <input id="mobile" type="row" class="form-control" name="mobile" placeholder="Mobile (Not required)">
{% endif %} {% endif %}
<label for="mobile">Not required</label>
<div class="form-group">{{ form.mobile.errors }}</div> <div class="form-group">{{ form.mobile.errors }}</div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -144,9 +142,17 @@ body {
<div class="form-group">{{ form.password2.errors }}</div> <div class="form-group">{{ form.password2.errors }}</div>
</div> </div>
<script src='https://www.google.com/recaptcha/api.js'></script>
<div class="form-group g-recaptcha" data-sitekey="6LcaYfgcAAAAAEX1GE9myoMEA2xVhWOrXqKUke_j"></div>
{% if messages %}
{% for message in messages %}
{{ message }}
{% endfor %}
{% endif %}
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-primary btn-lg">Sign Up</button> <button type="submit" class="btn btn-primary btn-lg">Sign Up</button>
</div> </div>
<div class="form-group">{{ form.non_field_errors }}</div> <div class="form-group">{{ form.non_field_errors }}</div>
</form> </form>
<div class="hint-text">Already have an account? <a href="{% url 'accounts:login' %}">Login here</a></div> <div class="hint-text">Already have an account? <a href="{% url 'accounts:login' %}">Login here</a></div>

View File

@ -1,11 +1,12 @@
from django.urls import path, include from django.urls import path, include
from .views import dashboard, RegisterUser from .views import dashboard, RegisterUser, success_registration, not_registered
from server.apps.accounts.utils import check_recaptcha
app_name = 'accounts' app_name = 'accounts'
urlpatterns = [ urlpatterns = [
path('', include("django.contrib.auth.urls")), path('', include("django.contrib.auth.urls")),
path('dashboard/', dashboard, name='dashboard'), path('dashboard/', dashboard, name='dashboard'),
path('registration/', RegisterUser.as_view(), name='registration') path('registration/', check_recaptcha(RegisterUser.as_view()), name='registration'),
path('complete_registration/<str:activation_token>', success_registration, name='success_registration'),
] ]

View File

@ -0,0 +1,27 @@
from django.conf import settings
from django.contrib import messages
from functools import wraps
import requests
def check_recaptcha(function):
@wraps(function)
def new_func(request, *args, **kwargs):
request.recaptcha_is_valid = None
if request.method == 'POST':
recaptcha_response = request.POST.get('g-recaptcha-response')
data = {
'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY,
'response': recaptcha_response
}
r = requests.post('https://www.google.com/recaptcha/api/siteverify', data=data)
result = r.json()
if result['success']:
request.recaptcha_is_valid = True
else:
request.recaptcha_is_valid = False
messages.error(request, 'Invalid reCAPTCHA. Please try again.')
return function(request, *args, **kwargs)
return new_func

View File

@ -2,33 +2,44 @@ from django.forms import BaseModelForm
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth import login from django.contrib.auth import login
from django.views.generic import CreateView from django.views.generic import CreateView
from django.http import HttpResponse from django.http import HttpResponse, HttpRequest
from server.apps.accounts.forms import CustomUserCreationForm from server.apps.accounts.forms import CustomUserCreationForm
from server.apps.accounts.models import CustomUser
from django.core.validators import validate_email from django.core.validators import validate_email
from .tasks import mail_send from django.core.exceptions import ObjectDoesNotExist
# Create your views here. # Create your views here.
def dashboard(request): def dashboard(request: HttpRequest) -> HttpResponse:
return render(request, "users/dashboard.html", {}) return render(request, "users/dashboard.html", {})
def not_registered(request: HttpRequest) -> HttpResponse:
return redirect('accounts:login')
def success_registration(request: HttpRequest, activation_token: str) -> HttpResponse:
try:
user = CustomUser.objects.get(activation_token=activation_token)
user.backend = 'django.contrib.auth.backends.ModelBackend'
user.is_staff = True
user.activation_token = ''
user.save()
login(request, user)
return redirect('admin:index')
except ObjectDoesNotExist:
message = 'Activation token not found'
return render(request, 'registration/info.html', {'message': message}, status=404)
class RegisterUser(CreateView): class RegisterUser(CreateView):
form_class = CustomUserCreationForm form_class = CustomUserCreationForm
template_name = 'users/register.html' template_name = 'users/register.html'
def form_valid(self, form: BaseModelForm) -> HttpResponse: def form_valid(self, form: BaseModelForm) -> HttpResponse:
user = form.save() if self.request.recaptcha_is_valid:
user.backend = 'django.contrib.auth.backends.ModelBackend' form.save()
validate_email(form.instance.email)
validate_email(form.instance.email) message = 'Please check your email for continue registration'
email = form.instance.email return render(self.request, 'registration/info.html', {'message': message})
username = f'{form.instance.first_name} {form.instance.last_name}' return render(self.request, 'users/register.html', self.get_context_data())
subject = 'Welcome to book bot administration'
mail_send(to_email=email, subject=subject, username=username)
login(self.request, user)
return redirect('admin:index')