V1
This commit is contained in:
parent
e8065d5378
commit
3c67ef4f81
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/tokens.cpython-311.pyc
Normal file
BIN
core/__pycache__/tokens.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
22
core/forms.py
Normal file
22
core/forms.py
Normal file
@ -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
|
||||||
14
core/templates/core/emails/activation_email.html
Normal file
14
core/templates/core/emails/activation_email.html
Normal file
@ -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 %}
|
||||||
15
core/templates/core/emails/password_reset_email.html
Normal file
15
core/templates/core/emails/password_reset_email.html
Normal file
@ -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 %}
|
||||||
1
core/templates/core/emails/password_reset_subject.txt
Normal file
1
core/templates/core/emails/password_reset_subject.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Password reset on {{ site_name }}
|
||||||
@ -27,10 +27,14 @@
|
|||||||
<input type="text" name="username" class="form-control" id="id_username" placeholder="Username" required>
|
<input type="text" name="username" class="form-control" id="id_username" placeholder="Username" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-2">
|
||||||
<label for="id_password" class="form-label fw-600">Password</label>
|
<label for="id_password" class="form-label fw-600">Password</label>
|
||||||
<input type="password" name="password" class="form-control" id="id_password" placeholder="Password" required>
|
<input type="password" name="password" class="form-control" id="id_password" placeholder="Password" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="text-end mb-4">
|
||||||
|
<a href="{% url 'password_reset' %}" class="text-decoration-none small fw-bold">Forgot password?</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary w-100 py-3">Log In</button>
|
<button type="submit" class="btn btn-primary w-100 py-3">Log In</button>
|
||||||
</form>
|
</form>
|
||||||
@ -42,4 +46,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
25
core/templates/core/password_reset_complete.html
Normal file
25
core/templates/core/password_reset_complete.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Password Reset Complete - Referral Rewards{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5 mt-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="card border-0 shadow-lg p-4 p-md-5 rounded-4 text-center">
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="display-1 text-success mb-3">
|
||||||
|
<i class="bi bi-check-circle"></i>
|
||||||
|
</div>
|
||||||
|
<h2 class="mb-3">Password Reset!</h2>
|
||||||
|
<p class="text-muted">Your password has been set. You may go ahead and log in now.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
<a href="{% url 'login' %}" class="btn btn-primary w-100 py-3">Log In</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
72
core/templates/core/password_reset_confirm.html
Normal file
72
core/templates/core/password_reset_confirm.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Set New Password - Referral Rewards{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5 mt-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="card border-0 shadow-lg p-4 p-md-5 rounded-4">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h2 class="mb-3">Set New Password</h2>
|
||||||
|
<p class="text-muted">Please enter your new password twice so we can verify you typed it correctly.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if validlink %}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
Please correct the errors below.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ field.id_for_label }}" class="form-label fw-600">{{ field.label }}</label>
|
||||||
|
{{ field }}
|
||||||
|
{% if field.help_text %}
|
||||||
|
<div class="form-text small">{{ field.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if field.errors %}
|
||||||
|
<div class="text-danger small mt-1">
|
||||||
|
{{ field.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100 py-3 mt-4">Change My Password</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
The password reset link was invalid, possibly because it has already been used. Please request a new password reset.
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'password_reset' %}" class="btn btn-primary w-100 py-3 mt-4">Request New Link</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Styling for Django generated form fields */
|
||||||
|
input[type="password"] {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #212529;
|
||||||
|
background-color: #fff;
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
27
core/templates/core/password_reset_done.html
Normal file
27
core/templates/core/password_reset_done.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Reset Email Sent - Referral Rewards{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5 mt-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="card border-0 shadow-lg p-4 p-md-5 rounded-4 text-center">
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="display-1 text-primary mb-3">
|
||||||
|
<i class="bi bi-envelope-check"></i>
|
||||||
|
</div>
|
||||||
|
<h2 class="mb-3">Email Sent</h2>
|
||||||
|
<p class="text-muted">We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-muted">If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder.</p>
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
<a href="{% url 'login' %}" class="btn btn-primary w-100 py-3">Return to Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
43
core/templates/core/password_reset_form.html
Normal file
43
core/templates/core/password_reset_form.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Reset Password - Referral Rewards{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5 mt-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="card border-0 shadow-lg p-4 p-md-5 rounded-4">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h2 class="mb-3">Reset Password</h2>
|
||||||
|
<p class="text-muted">Enter your email address and we'll send you a link to reset your password.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
Please correct the errors below.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="id_email" class="form-label fw-600">Email Address</label>
|
||||||
|
<input type="email" name="email" class="form-control" id="id_email" placeholder="Email address" required>
|
||||||
|
{% if form.email.errors %}
|
||||||
|
<div class="text-danger small mt-1">
|
||||||
|
{{ form.email.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100 py-3">Send Reset Link</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="text-center mt-5">
|
||||||
|
<p class="text-muted mb-0"><a href="{% url 'login' %}" class="text-decoration-none fw-bold">Back to Login</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -15,14 +15,23 @@
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div class="mb-3">
|
<div class="mb-3 {% if field.name == 'terms_accepted' %}form-check{% endif %}">
|
||||||
<label for="{{ field.id_for_label }}" class="form-label fw-600">{{ field.label }}</label>
|
{% if field.name == 'terms_accepted' %}
|
||||||
{{ field.errors }}
|
{{ field }}
|
||||||
<input type="{{ field.field.widget.input_type }}"
|
<label class="form-check-label ms-2" for="{{ field.id_for_label }}">
|
||||||
name="{{ field.name }}"
|
{{ field.label }}
|
||||||
class="form-control {% if field.errors %}is-invalid{% endif %}"
|
</label>
|
||||||
id="{{ field.id_for_label }}"
|
{{ field.errors }}
|
||||||
placeholder="Enter {{ field.label|lower }}">
|
{% else %}
|
||||||
|
<label for="{{ field.id_for_label }}" class="form-label fw-600">{{ field.label }}</label>
|
||||||
|
{{ field.errors }}
|
||||||
|
<input type="{{ field.field.widget.input_type }}"
|
||||||
|
name="{{ field.name }}"
|
||||||
|
class="form-control {% if field.errors %}is-invalid{% endif %}"
|
||||||
|
id="{{ field.id_for_label }}"
|
||||||
|
placeholder="Enter {{ field.label|lower }}">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if field.help_text %}
|
{% if field.help_text %}
|
||||||
<div class="form-text opacity-75 small">{{ field.help_text|safe }}</div>
|
<div class="form-text opacity-75 small">{{ field.help_text|safe }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -39,4 +48,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
10
core/tokens.py
Normal file
10
core/tokens.py
Normal file
@ -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()
|
||||||
19
core/urls.py
19
core/urls.py
@ -5,7 +5,26 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.home, name="home"),
|
path("", views.home, name="home"),
|
||||||
path("signup/", views.signup, name="signup"),
|
path("signup/", views.signup, name="signup"),
|
||||||
|
path("activate/<uidb64>/<token>/", views.activate, name="activate"),
|
||||||
path("dashboard/", views.dashboard, name="dashboard"),
|
path("dashboard/", views.dashboard, name="dashboard"),
|
||||||
path("login/", auth_views.LoginView.as_view(template_name='core/login.html'), name="login"),
|
path("login/", auth_views.LoginView.as_view(template_name='core/login.html'), name="login"),
|
||||||
path("logout/", views.logout_view, name="logout"),
|
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/<uidb64>/<token>/", 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"),
|
||||||
]
|
]
|
||||||
@ -1,9 +1,18 @@
|
|||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.contrib.auth import login, logout
|
from django.contrib.auth import login, logout, get_user_model
|
||||||
from django.contrib.auth.forms import UserCreationForm
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib import messages
|
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 .models import Profile
|
||||||
|
from .forms import SignupForm
|
||||||
|
from .tokens import account_activation_token
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
@ -14,21 +23,59 @@ def signup(request):
|
|||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
return redirect('dashboard')
|
return redirect('dashboard')
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = UserCreationForm(request.POST)
|
form = SignupForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.save()
|
user = form.save(commit=False)
|
||||||
login(request, user)
|
user.is_active = False
|
||||||
messages.success(request, "Welcome to Referral Rewards! Your account has been created.")
|
user.save()
|
||||||
return redirect('dashboard')
|
|
||||||
|
# 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:
|
else:
|
||||||
form = UserCreationForm()
|
form = SignupForm()
|
||||||
return render(request, 'core/signup.html', {'form': form})
|
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
|
@login_required
|
||||||
def dashboard(request):
|
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})
|
return render(request, 'core/dashboard.html', {'profile': profile})
|
||||||
|
|
||||||
def logout_view(request):
|
def logout_view(request):
|
||||||
logout(request)
|
logout(request)
|
||||||
return redirect('home')
|
return redirect('home')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user