From 3c67ef4f819e7711b5998936ed9428726dc9fb19 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 21 Jan 2026 23:22:59 +0000 Subject: [PATCH] V1 --- core/__pycache__/forms.cpython-311.pyc | Bin 0 -> 1797 bytes core/__pycache__/tokens.cpython-311.pyc | Bin 0 -> 924 bytes core/__pycache__/urls.cpython-311.pyc | Bin 869 -> 2155 bytes core/__pycache__/views.cpython-311.pyc | Bin 2241 -> 5273 bytes core/forms.py | 22 ++++++ .../core/emails/activation_email.html | 14 ++++ .../core/emails/password_reset_email.html | 15 ++++ .../core/emails/password_reset_subject.txt | 1 + core/templates/core/login.html | 8 +- .../core/password_reset_complete.html | 25 ++++++ .../core/password_reset_confirm.html | 72 ++++++++++++++++++ core/templates/core/password_reset_done.html | 27 +++++++ core/templates/core/password_reset_form.html | 43 +++++++++++ core/templates/core/signup.html | 27 ++++--- core/tokens.py | 10 +++ core/urls.py | 19 +++++ core/views.py | 67 +++++++++++++--- 17 files changed, 329 insertions(+), 21 deletions(-) create mode 100644 core/__pycache__/forms.cpython-311.pyc create mode 100644 core/__pycache__/tokens.cpython-311.pyc create mode 100644 core/forms.py create mode 100644 core/templates/core/emails/activation_email.html create mode 100644 core/templates/core/emails/password_reset_email.html create mode 100644 core/templates/core/emails/password_reset_subject.txt create mode 100644 core/templates/core/password_reset_complete.html create mode 100644 core/templates/core/password_reset_confirm.html create mode 100644 core/templates/core/password_reset_done.html create mode 100644 core/templates/core/password_reset_form.html create mode 100644 core/tokens.py diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e981af509af5f291e59aba297ffbcb181f65d71f GIT binary patch literal 1797 zcmb7E&2QX96rb_h`xS4UrCCUz2&bS%F3K(vqKASI5G7F*kwZVEW)E17XOej9U2n#A z6QV`Rp%U9FQn{dBIOG&S$pLZYpV&xyutw^M6Ss|$1LV{8jzlF&RN z@?%64s_dr_I)I+EYMP|)A|vyUFb&L=RgcwuP~`hQW$+yp-x)@&!0{S$Fle98j6d0P z)R?cl@AK8AVF zlXjSW4M8?WmjiTpnybaUiOAQY@Lvi`OH$Z{Q`)4JujxeQKbfx7%H4J@&DjC9G!@~mk# ztU5JKo;S_9M?zQVs%dV7mYa+)akX55LFzJ*kX#na@bs!fUE=?aPQZuGrYq}SomRq? zun~k6x<&0U@K|NjV-NkNWz&l7F)NO<&xOuIoEY9Fe^bV*Aw_7mF6b z>Cb@pcL%-D#-42MZa!;0ZS8&5ZXIbxS2NmjRGR3PZtQJ#OO>{~n~z3{J%n@PkvjTp z;pxJY#ofjBVx(PdkHlAC;Cn`aiDlVaLR=L>u)_1RqK4)~X2gf*>NlPz5XCNDm=QIw z;%zO6D&SY*qA`VZ@ zUTe>G)oXjCbL&AzT|QEmyXx}JY*d>1>3+8~6%|X-mD`bay&ZP7>%UEPKAr6ta~*B| zNSp6!^SzAxcKR5BI2B@Nsi#WH=RNeM=x85wwU3U)$mzv!R#C=!=zoYLxdoafBg50A zW;NElgw=NwqYQtRfH^B=D-71laY6b?bxvFs5Z7A5X-w2RuynGCNR_kf7fHQ;$!>xv tSoNDAdMS)?gvL9=Z-hSX48IY&)fs+!auEaOH$|s7c`V$gmyE?HdI`+)uhRek literal 0 HcmV?d00001 diff --git a/core/__pycache__/tokens.cpython-311.pyc b/core/__pycache__/tokens.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..027eea55e53e07be26a653ff26e8ffbffa94f32c GIT binary patch literal 924 zcmah{&1(}u6o0e3!6qcBphz)70)mjsuHa2WAieZbil`R@I!t#*^R>I_%xtx#haP$n zJa`bQpx21xpGlwxS@0wtyrslbPrlhTD`^hCoq6-V=QnTPmr|(!NJr22y3Yu}53MXH z_g9#2qHqcxcw_)RX%hklY=LK-fM;?tG6BZ8Q)(LqaASnZcwvIJ=~<7Vp8KUObrM$| zGAUn$!h6IeS5Lw|54L!~1yiALvn&)4Q9|?Uc$@-Nkb z4)(Dxhs@=U8w&0y-A!o^58|uTXMIjPOm^r#8$|r@?tfZkU(V3&k`A(C_*q%pXQe_CmrRT>l6NLO<8A866vbpQYW literal 0 HcmV?d00001 diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 9977bb189e8d31b07cbc6eafe49cd4b93c5738d0..736280a3733a6676becdfd7f24d317870a109820 100644 GIT binary patch literal 2155 zcmaJ>OHbTZ7`>jko+rZu31?9cEZOITi2>MBweuF_Rj{jMLQ448Vx_VxMBJ?DPkef*Zo zWe{9%?*CAK!6NiG{Rppo(0IEQL+Do|A&F_ACR1S;+Kx41&3FYvI}U9Y+HnTO5Md-t z;=74ZL}HH;hjD~n!R=ojCkdQnh)AhOP8v9=E+-So$pR6YE6JvIJ4(fV^X?wbomfw{T1B7$cm9_Q=oPs zuh4AiH5Hp>9NZSQ9eA^k>g2Kdn$C-Z9K0hp35~YICNYv~eY>T%1t;M*jB&B1)gOr( z5tjA3w7K}nimtV;Xe8k!>l)bm4I|$cRrRr~NOKBNi6$5mo>YidJ+!(dD@{+>;Jq+u ziuHynoML&XPb>WoP1QGdNllyA_B3O-s}cB1a*GJFSfLBn;XK7^ZzjV*|5I>Q)oV4P zs#RTSIQ_jNGls(N_g$h>r33JCaJaBjPy`w=J`6!$X|5)>w(3gr?Bp4jqY4?&{UVK> zN*=^u$_Oll*_c-VWq?*;B(x015oj2rA>JKI(Ay|RTl--@xr<@vuBacqGdt*HrCqVL zEzd(GRfV2`4i_L`tts`*c~RGP6nX+U8QQFR3tpiIs6tOjg&uDTJ--xsbUEqUbeVnw zyTVC|s>gP+{sQzr1~!6Kc#keZk2u@|-GbVC@MsTw)33f489Np6^YKDi$OQJLMwFKN zJ9-O4Lhn8n=)M&6O}(Y*0@))qU6U07w!%ZTE!K$O)sU88zWw97)N)gn^afc`u0UtH zvsDM4Zk%Bl8x_pnzm2Y%y?+~VX7ArkAU6Hv*3-eKU;k2hzGY&*gLw<{HckOLw*T3) z+Xs6lUg+Qj3om%6ndT4l7eD>EZsOGrUbXP5PcH21&whUSy@{7Pc*(*`K03Ppsm0Bl zaKbSsm@8kJoY3JmEN;X6_P&X$9bC0=wQFV#@h`tHak+!b7A^;4Gt=YG*R8^}lfw1m z!gYJ3Y!`0Ylb_o|AGv8XafA5>u@i}Z>BG&S+=QED*-60INx;}ix6ez^OkX+}w~9AT zifhNkH4t&*Xlfn$#-^YzJpSq{x@?S{285jkgq`l%U;U$EmA^YFKR7Nwut%=hLxRhq zMG-p37ojsefSTjoWSlJl#+CqMOKvJKo<68p{MreB_n5zHk1T<4=^iyMQR5OA^VE2T V8qZMU8Fy|hAA6FeKV=H$_&-Y3Uorpy delta 193 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 -
+
+ + @@ -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')