diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..e981af5 Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/tokens.cpython-311.pyc b/core/__pycache__/tokens.cpython-311.pyc new file mode 100644 index 0000000..027eea5 Binary files /dev/null and b/core/__pycache__/tokens.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 9977bb1..736280a 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index a0a46b2..1a45eb4 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..be81d44 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,22 @@ +from django import forms +from django.contrib.auth.models import User +from django.contrib.auth.forms import UserCreationForm + +class SignupForm(UserCreationForm): + email = forms.EmailField(required=True, help_text="Required. A valid email address.") + terms_accepted = forms.BooleanField( + required=True, + label="I accept the standard terms and conditions", + widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) + ) + + class Meta(UserCreationForm.Meta): + model = User + fields = UserCreationForm.Meta.fields + ('email',) + + def save(self, commit=True): + user = super().save(commit=False) + user.email = self.cleaned_data["email"] + if commit: + user.save() + return user diff --git a/core/templates/core/emails/activation_email.html b/core/templates/core/emails/activation_email.html new file mode 100644 index 0000000..5252eee --- /dev/null +++ b/core/templates/core/emails/activation_email.html @@ -0,0 +1,14 @@ +{% autoescape off %} +Hi {{ user.username }}, + +Welcome to Referral Rewards! + +Please click on the link below to confirm your registration and activate your account: + +{{ protocol }}://{{ domain }}{% url 'activate' uidb64=uid token=token %} + +If you did not sign up for this account, please ignore this email. + +Best regards, +The Referral Rewards Team +{% endautoescape %} \ No newline at end of file diff --git a/core/templates/core/emails/password_reset_email.html b/core/templates/core/emails/password_reset_email.html new file mode 100644 index 0000000..282c4cf --- /dev/null +++ b/core/templates/core/emails/password_reset_email.html @@ -0,0 +1,15 @@ +{% autoescape off %} +You're receiving this email because you requested a password reset for your user account at {{ site_name }}. + +Please go to the following page and choose a new password: + +{% block reset_link %} +{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} +{% endblock %} + +Your username, in case you've forgotten: {{ user.get_username }} + +Thanks for using our site! + +The {{ site_name }} team +{% endautoescape %} diff --git a/core/templates/core/emails/password_reset_subject.txt b/core/templates/core/emails/password_reset_subject.txt new file mode 100644 index 0000000..fc16b16 --- /dev/null +++ b/core/templates/core/emails/password_reset_subject.txt @@ -0,0 +1 @@ +Password reset on {{ site_name }} diff --git a/core/templates/core/login.html b/core/templates/core/login.html index a9e978a..34416b3 100644 --- a/core/templates/core/login.html +++ b/core/templates/core/login.html @@ -27,10 +27,14 @@ -
+
+ +
+ Forgot password? +
@@ -42,4 +46,4 @@
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/password_reset_complete.html b/core/templates/core/password_reset_complete.html new file mode 100644 index 0000000..9ccce27 --- /dev/null +++ b/core/templates/core/password_reset_complete.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} + +{% block title %}Password Reset Complete - Referral Rewards{% endblock %} + +{% block content %} +
+
+
+
+
+
+ +
+

Password Reset!

+

Your password has been set. You may go ahead and log in now.

+
+ +
+ Log In +
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/password_reset_confirm.html b/core/templates/core/password_reset_confirm.html new file mode 100644 index 0000000..d985905 --- /dev/null +++ b/core/templates/core/password_reset_confirm.html @@ -0,0 +1,72 @@ +{% extends 'base.html' %} + +{% block title %}Set New Password - Referral Rewards{% endblock %} + +{% block content %} +
+
+
+
+
+

Set New Password

+

Please enter your new password twice so we can verify you typed it correctly.

+
+ + {% if validlink %} +
+ {% csrf_token %} + {% if form.errors %} +
+ Please correct the errors below. +
+ {% endif %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% if field.errors %} +
+ {{ field.errors }} +
+ {% endif %} +
+ {% endfor %} + + +
+ {% else %} +
+ The password reset link was invalid, possibly because it has already been used. Please request a new password reset. +
+ Request New Link + {% endif %} +
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/password_reset_done.html b/core/templates/core/password_reset_done.html new file mode 100644 index 0000000..6d141f7 --- /dev/null +++ b/core/templates/core/password_reset_done.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block title %}Reset Email Sent - Referral Rewards{% endblock %} + +{% block content %} +
+
+
+
+
+
+ +
+

Email Sent

+

We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.

+
+ +

If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder.

+ + +
+
+
+
+{% endblock %} diff --git a/core/templates/core/password_reset_form.html b/core/templates/core/password_reset_form.html new file mode 100644 index 0000000..b6e89fa --- /dev/null +++ b/core/templates/core/password_reset_form.html @@ -0,0 +1,43 @@ +{% extends 'base.html' %} + +{% block title %}Reset Password - Referral Rewards{% endblock %} + +{% block content %} +
+
+
+
+
+

Reset Password

+

Enter your email address and we'll send you a link to reset your password.

+
+ +
+ {% csrf_token %} + {% if form.errors %} +
+ Please correct the errors below. +
+ {% endif %} + +
+ + + {% if form.email.errors %} +
+ {{ form.email.errors }} +
+ {% endif %} +
+ + +
+ + +
+
+
+
+{% endblock %} diff --git a/core/templates/core/signup.html b/core/templates/core/signup.html index 4c4fd28..bee16fe 100644 --- a/core/templates/core/signup.html +++ b/core/templates/core/signup.html @@ -15,14 +15,23 @@
{% csrf_token %} {% for field in form %} -
- - {{ field.errors }} - +
+ {% if field.name == 'terms_accepted' %} + {{ field }} + + {{ field.errors }} + {% else %} + + {{ field.errors }} + + {% endif %} + {% if field.help_text %}
{{ field.help_text|safe }}
{% endif %} @@ -39,4 +48,4 @@
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/tokens.py b/core/tokens.py new file mode 100644 index 0000000..4bb612e --- /dev/null +++ b/core/tokens.py @@ -0,0 +1,10 @@ +from django.contrib.auth.tokens import PasswordResetTokenGenerator + +class AccountActivationTokenGenerator(PasswordResetTokenGenerator): + def _make_hash_value(self, user, timestamp): + return ( + str(user.pk) + str(timestamp) + + str(user.is_active) + ) + +account_activation_token = AccountActivationTokenGenerator() \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 8493052..3578e5a 100644 --- a/core/urls.py +++ b/core/urls.py @@ -5,7 +5,26 @@ from . import views urlpatterns = [ path("", views.home, name="home"), path("signup/", views.signup, name="signup"), + path("activate///", views.activate, name="activate"), path("dashboard/", views.dashboard, name="dashboard"), path("login/", auth_views.LoginView.as_view(template_name='core/login.html'), name="login"), path("logout/", views.logout_view, name="logout"), + + # Password Reset + path("password-reset/", auth_views.PasswordResetView.as_view( + template_name='core/password_reset_form.html', + email_template_name='core/emails/password_reset_email.html', + subject_template_name='core/emails/password_reset_subject.txt', + success_url='/password-reset/done/' + ), name="password_reset"), + path("password-reset/done/", auth_views.PasswordResetDoneView.as_view( + template_name='core/password_reset_done.html' + ), name="password_reset_done"), + path("password-reset-confirm///", auth_views.PasswordResetConfirmView.as_view( + template_name='core/password_reset_confirm.html', + success_url='/password-reset-complete/' + ), name="password_reset_confirm"), + path("password-reset-complete/", auth_views.PasswordResetCompleteView.as_view( + template_name='core/password_reset_complete.html' + ), name="password_reset_complete"), ] \ No newline at end of file diff --git a/core/views.py b/core/views.py index 4afd890..1066a46 100644 --- a/core/views.py +++ b/core/views.py @@ -1,9 +1,18 @@ from django.shortcuts import render, redirect -from django.contrib.auth import login, logout -from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth import login, logout, get_user_model from django.contrib.auth.decorators import login_required from django.contrib import messages +from django.contrib.sites.shortcuts import get_current_site +from django.utils.encoding import force_bytes, force_str +from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode +from django.template.loader import render_to_string +from django.core.mail import EmailMessage + from .models import Profile +from .forms import SignupForm +from .tokens import account_activation_token + +User = get_user_model() def home(request): if request.user.is_authenticated: @@ -14,21 +23,59 @@ def signup(request): if request.user.is_authenticated: return redirect('dashboard') if request.method == 'POST': - form = UserCreationForm(request.POST) + form = SignupForm(request.POST) if form.is_valid(): - user = form.save() - login(request, user) - messages.success(request, "Welcome to Referral Rewards! Your account has been created.") - return redirect('dashboard') + user = form.save(commit=False) + user.is_active = False + user.save() + + # Send activation email + current_site = get_current_site(request) + mail_subject = 'Activate your Referral Rewards account.' + message = render_to_string('core/emails/activation_email.html', { + 'user': user, + 'domain': current_site.domain, + 'protocol': 'https' if request.is_secure() else 'http', + 'uid': urlsafe_base64_encode(force_bytes(user.pk)), + 'token': account_activation_token.make_token(user), + }) + to_email = form.cleaned_data.get('email') + email = EmailMessage( + mail_subject, message, to=[to_email] + ) + try: + email.send() + messages.success(request, 'Please confirm your email address to complete the registration. Check your inbox.') + except Exception as e: + messages.error(request, f'Error sending email: {str(e)}. Please contact support.') + + return redirect('login') else: - form = UserCreationForm() + form = SignupForm() return render(request, 'core/signup.html', {'form': form}) +def activate(request, uidb64, token): + try: + uid = force_str(urlsafe_base64_decode(uidb64)) + user = User.objects.get(pk=uid) + except(TypeError, ValueError, OverflowError, User.DoesNotExist): + user = None + if user is not None and account_activation_token.check_token(user, token): + user.is_active = True + user.save() + login(request, user) + messages.success(request, 'Thank you for your email confirmation. Now you can enjoy our services.') + return redirect('dashboard') + else: + messages.error(request, 'Activation link is invalid!') + return redirect('home') + @login_required def dashboard(request): - profile = request.user.profile + # Ensure profile exists (though signal should handle it) + profile, created = Profile.objects.get_or_create(user=request.user) return render(request, 'core/dashboard.html', {'profile': profile}) def logout_view(request): logout(request) - return redirect('home') \ No newline at end of file + return redirect('home')