Self registration with mail validation and admin activation implemented
This commit is contained in:
parent
c9532aaf37
commit
d18f439cc2
11
README.md
11
README.md
@ -18,6 +18,11 @@ Add the following line to the list ```INSTALLED_APPS```:
|
||||
```
|
||||
'users.apps.UsersConfig',
|
||||
```
|
||||
and this line to let django know the login url
|
||||
```
|
||||
LOGIN_URL = 'users-login'
|
||||
```
|
||||
|
||||
|
||||
### Configurations in your urls.py
|
||||
and add the following line to the list ```urlpatterns```:
|
||||
@ -31,6 +36,12 @@ All parameters can be added in the django ```settings.py``` or in a ```config.py
|
||||
#### USERS_SELF_REGISTRATION
|
||||
This parameter can be ```True``` or ```False```. It enables or disables the self registration.
|
||||
|
||||
#### USERS_MAIL_VALIDATION
|
||||
This parameter can be ```True``` or ```False```. It enables or disables the mail validation after self registration.
|
||||
|
||||
#### USERS_ADMIN_ACTIVATION
|
||||
This parameter can be ```True``` or ```False```. It enables or disables the activation by an admin after mail validation.
|
||||
|
||||
|
||||
## Usage
|
||||
### Actionabr
|
||||
|
@ -11,11 +11,13 @@ PROFILE_ENTRY_UID = 'profile-main'
|
||||
REGISTER_ENTRY_UID = 'register-main'
|
||||
|
||||
|
||||
def context_adaption(context, request, title):
|
||||
def context_adaption(context, request, title, **kwargs):
|
||||
context.set_additional_title(title)
|
||||
menubar(context[context.MENUBAR], request)
|
||||
context[context.NAVIGATIONBAR].append_entry(*empty_entry_parameters(request))
|
||||
actionbar(context[context.ACTIONBAR], request)
|
||||
for key in kwargs:
|
||||
context[key] = kwargs[key]
|
||||
|
||||
|
||||
def menubar(bar, request):
|
||||
|
58
emails.py
Normal file
58
emails.py
Normal file
@ -0,0 +1,58 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core.mail import EmailMessage, send_mail, mail_admins
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
from . tokens import generate_token
|
||||
from users import parameter
|
||||
|
||||
|
||||
def send_activation_mail(new_user, request):
|
||||
email_subject = "Activate Account"
|
||||
message2 = render_to_string('users/email_activation.html', {
|
||||
'name': new_user.first_name,
|
||||
'base_url': request.build_absolute_uri('/')[:-1],
|
||||
'pk': new_user.pk,
|
||||
})
|
||||
email = EmailMessage(
|
||||
email_subject,
|
||||
message2,
|
||||
settings.EMAIL_HOST_USER,
|
||||
)
|
||||
mail_admins(email_subject, message2, fail_silently=True)
|
||||
|
||||
|
||||
def send_validation_failed(uid, token):
|
||||
subject = "E-Mail validation failed!"
|
||||
message = f"uid = {uid}\ntoken = '{token}'"
|
||||
mail_admins(subject, message, fail_silently=True)
|
||||
|
||||
|
||||
def send_welcome_mail(new_user: User):
|
||||
subject = "Welcome!"
|
||||
message = """Hello %(username)s.
|
||||
|
||||
Welcome!
|
||||
|
||||
Thank you for registration.""" % {"username": new_user.first_name}
|
||||
message += "\n\n" + parameter.registration_flow_description(new_user.username)
|
||||
send_mail(subject, message, settings.EMAIL_FROM, [new_user.email], fail_silently=True)
|
||||
|
||||
|
||||
def send_validation_mail(new_user, request):
|
||||
email_subject = "Confirm your Email"
|
||||
message2 = render_to_string('users/email_confirmation.html', {
|
||||
'name': new_user.first_name,
|
||||
'base_url': request.build_absolute_uri('/')[:-1],
|
||||
'uid': urlsafe_base64_encode(force_bytes(new_user.pk)),
|
||||
'token': generate_token.make_token(new_user)
|
||||
})
|
||||
email = EmailMessage(
|
||||
email_subject,
|
||||
message2,
|
||||
settings.EMAIL_HOST_USER,
|
||||
[new_user.email],
|
||||
)
|
||||
send_mail(email_subject, message2, settings.EMAIL_FROM, [new_user.email], fail_silently=True)
|
12
forms.py
12
forms.py
@ -1,6 +1,6 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||
from .models import UserProfile
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ class UserRegistrationForm(UserCreationForm):
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['username', 'email', 'password1', 'password2']
|
||||
fields = ['username', 'email', 'first_name', 'last_name', 'password1', 'password2']
|
||||
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
@ -22,3 +22,11 @@ class UserProfileFormLanguageOnly(forms.ModelForm):
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = ['language_code']
|
||||
|
||||
|
||||
class UserActivationForm(UserChangeForm):
|
||||
password = None
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['is_active', 'is_staff']
|
||||
|
18
migrations/0003_userprofile_mail_validated.py
Normal file
18
migrations/0003_userprofile_mail_validated.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-26 12:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0002_alter_userprofile_id_alter_userprofile_language_code_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userprofile',
|
||||
name='mail_validated',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
@ -29,6 +29,7 @@ class UserProfile(models.Model):
|
||||
user = models.OneToOneField(User, unique=True, on_delete=models.CASCADE)
|
||||
timezone = models.CharField(max_length=150, default='UTC', choices=[(t, t) for t in pytz.common_timezones])
|
||||
language_code = models.CharField(max_length=150, default='en', choices=settings.LANGUAGES)
|
||||
mail_validated = models.BooleanField(default=False)
|
||||
|
||||
def export_key(self):
|
||||
return self.user.username
|
||||
|
20
parameter.py
20
parameter.py
@ -1,15 +1,16 @@
|
||||
import config
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
USERS_SELF_REGISTRATION = "USERS_SELF_REGISTRATION"
|
||||
# USERS_MAIL_VALIDATION = "USERS_MAIL_VALIDATION"
|
||||
# USERS_MAIL_INFORMATION = "USERS_MAIL_INFORMATION"
|
||||
USERS_MAIL_VALIDATION = "USERS_MAIL_VALIDATION"
|
||||
USERS_ADMIN_ACTIVATION = "USERS_ADMIN_ACTIVATION"
|
||||
|
||||
|
||||
DEFAULTS = {
|
||||
USERS_SELF_REGISTRATION: False,
|
||||
# USERS_MAIL_VALIDATION: True,
|
||||
# USERS_MAIL_INFORMATION: True,
|
||||
USERS_MAIL_VALIDATION: True,
|
||||
USERS_ADMIN_ACTIVATION: True,
|
||||
}
|
||||
|
||||
|
||||
@ -24,3 +25,14 @@ def get(key):
|
||||
data = DEFAULTS.get(key)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def registration_flow_description(username):
|
||||
if not get(USERS_MAIL_VALIDATION) and not get(USERS_ADMIN_ACTIVATION):
|
||||
return _("Your account has been created. You are now able to Login as %s.") % username
|
||||
elif get(USERS_MAIL_VALIDATION) and get(USERS_ADMIN_ACTIVATION):
|
||||
return _("Your account has been created. You'll get an email to validate your account. Then you have to wait for the activation by an administrator.")
|
||||
elif get(USERS_MAIL_VALIDATION):
|
||||
return _("Your account has been created. You'll get an email to validate your account. After validation you are able to Login as %s.") % username
|
||||
else:
|
||||
return _("Your account has been created. You have to wait for the activation by an administrator.")
|
||||
|
12
templates/users/activate.html
Normal file
12
templates/users/activate.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends "themes/"|add:settings.page_theme|add:"/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="submit" value="{% trans "Submit" %}" class="button" />
|
||||
<input type="submit" name="delete" value="{% trans "Delete user" %}" class="button" />
|
||||
</form>
|
||||
{% endblock content %}
|
8
templates/users/email_activation.html
Normal file
8
templates/users/email_activation.html
Normal file
@ -0,0 +1,8 @@
|
||||
{% autoescape off %}
|
||||
|
||||
Hello admins.
|
||||
|
||||
The User {{ username }} validated the email address. Please follow the link below to activate or delete the account.
|
||||
Activation Link: {{ base_url }}{% url 'users-activate' pk=pk %}
|
||||
|
||||
{% endautoescape %}
|
8
templates/users/email_confirmation.html
Normal file
8
templates/users/email_confirmation.html
Normal file
@ -0,0 +1,8 @@
|
||||
{% autoescape off %}
|
||||
|
||||
Hello {{ name }}.
|
||||
|
||||
Please verify your email by clicking on the following link.
|
||||
Confirmation Link: {{ base_url }}{% url 'users-validate' uidb64=uid token=token %}
|
||||
|
||||
{% endautoescape %}
|
9
tokens.py
Normal file
9
tokens.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||
|
||||
|
||||
class AppTokenGenerator(PasswordResetTokenGenerator):
|
||||
def _make_hash_value(self, user, timestamp):
|
||||
return f"{user.is_active}{user.pk}{timestamp}"
|
||||
|
||||
|
||||
generate_token = AppTokenGenerator()
|
2
urls.py
2
urls.py
@ -7,4 +7,6 @@ urlpatterns = [
|
||||
path('password_recovery', views.password_recovery, name='users-password-recovery'),
|
||||
path('profile', views.profile, name='users-profile'),
|
||||
path('register', views.register, name='users-register'),
|
||||
path('validate/<uidb64>/<token>', views.validate, name='users-validate'),
|
||||
path('activate/<pk>', views.activate, name='users-activate'),
|
||||
]
|
||||
|
109
views.py
109
views.py
@ -7,12 +7,17 @@ from django.contrib.auth import login as django_login
|
||||
from django.contrib.auth import logout as django_logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.http import urlsafe_base64_decode
|
||||
from django.utils.translation import gettext as _
|
||||
from .forms import UserRegistrationForm, UserProfileForm
|
||||
from .forms import UserRegistrationForm, UserProfileForm, UserActivationForm
|
||||
import logging
|
||||
from .models import get_userprofile
|
||||
from themes import Context
|
||||
from . tokens import generate_token
|
||||
import users
|
||||
from users import emails
|
||||
from users import parameter
|
||||
|
||||
logger = logging.getLogger(settings.ROOT_LOGGER_NAME).getChild(__name__)
|
||||
@ -66,9 +71,16 @@ def register(request):
|
||||
else:
|
||||
form = UserRegistrationForm(request.POST)
|
||||
if form.is_valid():
|
||||
# Deactivate the user, if validation or activation is required
|
||||
if parameter.get(parameter.USERS_MAIL_VALIDATION) or parameter.get(parameter.USERS_ADMIN_ACTIVATION):
|
||||
form.instance.is_active = False
|
||||
form.save()
|
||||
messages.success(request, _('Your account has been created! You are able to log in as %(username)s.') %
|
||||
{'username': form.cleaned_data.get('username')})
|
||||
# Send welcome message
|
||||
emails.send_welcome_mail(form.instance)
|
||||
if parameter.get(parameter.USERS_MAIL_VALIDATION):
|
||||
emails.send_validation_mail(form.instance, request)
|
||||
# Add success message
|
||||
messages.success(request, parameter.registration_flow_description(form.cleaned_data.get('username')))
|
||||
return redirect('users-login')
|
||||
else:
|
||||
messages.error(request, _('Registration failed!'))
|
||||
@ -95,12 +107,17 @@ def login(request):
|
||||
messages.success(request, _('You are now logged in as %(username)s.') % {'username': username})
|
||||
return redirect(request.GET.get('next') or '/')
|
||||
else:
|
||||
if parameter.get(parameter.USERS_SELF_REGISTRATION):
|
||||
messages.error(request, _('Login failed! You can do a password recorvery <a href="%(url_recover)s">here</a> or you can register <a href="%(url_register)s">here</a>.') %
|
||||
{'url_register': users.url_register(request), 'url_recover': users.url_password_recovery(request)})
|
||||
username = form.cleaned_data.get('username')
|
||||
user = User.objects.get(username=username)
|
||||
if user.is_active:
|
||||
if parameter.get(parameter.USERS_SELF_REGISTRATION):
|
||||
messages.error(request, _('Login failed! You can do a password recorvery <a href="%(url_recover)s">here</a> or you can register <a href="%(url_register)s">here</a>.') %
|
||||
{'url_register': users.url_register(request), 'url_recover': users.url_password_recovery(request)})
|
||||
else:
|
||||
messages.error(request, _('Login failed! You can do a password recorvery <a href="%(url_recover)s">here</a>.') %
|
||||
{'url_recover': users.url_password_recovery(request)})
|
||||
else:
|
||||
messages.error(request, _('Login failed! You can do a password recorvery <a href="%(url_recover)s">here</a>.') %
|
||||
{'url_recover': users.url_password_recovery(request)})
|
||||
messages.info(request, _("The account is deactivated. Confirm your email adress and wait for the administrator to activate your account."))
|
||||
|
||||
context['form'] = form
|
||||
return render(request, 'users/login.html', context)
|
||||
@ -120,3 +137,79 @@ def logout(request):
|
||||
for variable in session_cache:
|
||||
request.session[variable] = session_cache[variable]
|
||||
return redirect(request.GET.get('next') or '/')
|
||||
|
||||
|
||||
def validate(request, uidb64, token):
|
||||
context = Context(request) # needs to be executed first because of time mesurement
|
||||
try:
|
||||
uid = force_str(urlsafe_base64_decode(uidb64))
|
||||
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
|
||||
uid = None
|
||||
myuser = None
|
||||
else:
|
||||
try:
|
||||
myuser = User.objects.get(pk=uid)
|
||||
except User.DoesNotExist:
|
||||
myuser = None
|
||||
|
||||
if myuser is not None and generate_token.check_token(myuser, token):
|
||||
# Store mail validation to user profile
|
||||
profile = get_userprofile(myuser)
|
||||
profile.mail_validated = True
|
||||
profile.save()
|
||||
if not parameter.get(parameter.USERS_ADMIN_ACTIVATION):
|
||||
# Activate user
|
||||
myuser.is_active = True
|
||||
myuser.save()
|
||||
messages.success(request, _("Your Account has been activated."))
|
||||
return redirect('users-login')
|
||||
else:
|
||||
emails.send_activation_mail(myuser, request)
|
||||
messages.success(request, _("Your Email has been validated. Wait for the administrator to activate your account"))
|
||||
return redirect("/")
|
||||
else:
|
||||
context_adaption(
|
||||
context,
|
||||
request,
|
||||
_('Validation failed'),
|
||||
)
|
||||
messages.info(request, _("Vaildation failed. The system administrator will be informed."))
|
||||
emails.send_validation_failed(uid, token)
|
||||
return redirect("/")
|
||||
|
||||
|
||||
@login_required
|
||||
def activate(request, pk):
|
||||
context = Context(request) # needs to be executed first because of time mesurement
|
||||
if not request.POST:
|
||||
if request.user.is_superuser:
|
||||
user_to_be_activated = User.objects.get(pk=pk)
|
||||
if not user_to_be_activated.is_active:
|
||||
user_to_be_activated.is_active = True
|
||||
form = UserActivationForm(instance=user_to_be_activated)
|
||||
context_adaption(
|
||||
context,
|
||||
request,
|
||||
_('Activation of user: %s') % f"{user_to_be_activated.username} - {user_to_be_activated.email}",
|
||||
form=form,
|
||||
)
|
||||
return render(request, 'users/activate.html', context)
|
||||
else:
|
||||
messages.error(request, _("The user %s is already active.") % user_to_be_activated.username)
|
||||
else:
|
||||
messages.error(request, _("You are no administrator. Log in as administrator and try again!"))
|
||||
else:
|
||||
submit = request.POST.get("submit")
|
||||
delete = request.POST.get("delete")
|
||||
user_to_be_activated = User.objects.get(pk=pk)
|
||||
if submit:
|
||||
form = UserActivationForm(request.POST, instance=user_to_be_activated)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.info(request, _("User permissions changed."))
|
||||
else:
|
||||
messages.error(request, _("Error while processing user change form"))
|
||||
if delete:
|
||||
user_to_be_activated.delete()
|
||||
messages.info(request, _("User deleted."))
|
||||
return redirect("/")
|
||||
|
Loading…
x
Reference in New Issue
Block a user