diff --git a/README.md b/README.md index c8273f2..a2e47d0 100644 --- a/README.md +++ b/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 diff --git a/context.py b/context.py index ceda0ca..2f7843e 100644 --- a/context.py +++ b/context.py @@ -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): diff --git a/emails.py b/emails.py new file mode 100644 index 0000000..01e20ce --- /dev/null +++ b/emails.py @@ -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) diff --git a/forms.py b/forms.py index e465bf7..8c45568 100644 --- a/forms.py +++ b/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'] diff --git a/migrations/0003_userprofile_mail_validated.py b/migrations/0003_userprofile_mail_validated.py new file mode 100644 index 0000000..8756d7a --- /dev/null +++ b/migrations/0003_userprofile_mail_validated.py @@ -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), + ), + ] diff --git a/models.py b/models.py index bf0fbf1..0ddcaf0 100644 --- a/models.py +++ b/models.py @@ -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 diff --git a/parameter.py b/parameter.py index a478afa..c3b33a6 100644 --- a/parameter.py +++ b/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.") diff --git a/templates/users/activate.html b/templates/users/activate.html new file mode 100644 index 0000000..d124b10 --- /dev/null +++ b/templates/users/activate.html @@ -0,0 +1,12 @@ +{% extends "themes/"|add:settings.page_theme|add:"/base.html" %} +{% load i18n %} + +{% block content %} + +
+{% endblock content %} diff --git a/templates/users/email_activation.html b/templates/users/email_activation.html new file mode 100644 index 0000000..917580e --- /dev/null +++ b/templates/users/email_activation.html @@ -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 %} diff --git a/templates/users/email_confirmation.html b/templates/users/email_confirmation.html new file mode 100644 index 0000000..1d19a80 --- /dev/null +++ b/templates/users/email_confirmation.html @@ -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 %} diff --git a/tokens.py b/tokens.py new file mode 100644 index 0000000..9987d9d --- /dev/null +++ b/tokens.py @@ -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() diff --git a/urls.py b/urls.py index aa6537a..065bc68 100644 --- a/urls.py +++ b/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/