From 82aa5f885128afd81f963124c9312f820abe5f35 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 28 Feb 2026 22:50:06 +0000 Subject: [PATCH] Referral Rewards V1 --- config/__pycache__/settings.cpython-311.pyc | Bin 5714 -> 5853 bytes config/settings.py | 20 +-- core/__pycache__/forms.cpython-311.pyc | Bin 1797 -> 2289 bytes core/__pycache__/middleware.cpython-311.pyc | Bin 0 -> 955 bytes core/__pycache__/models.cpython-311.pyc | Bin 2608 -> 3938 bytes core/__pycache__/urls.cpython-311.pyc | Bin 2155 -> 2260 bytes core/__pycache__/views.cpython-311.pyc | Bin 5273 -> 9237 bytes core/forms.py | 3 + core/management/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 168 bytes core/management/commands/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 177 bytes .../resend_activation.cpython-311.pyc | Bin 0 -> 3680 bytes core/management/commands/resend_activation.py | 48 +++++++ core/middleware.py | 11 ++ .../0002_profile_referred_by_referral.py | 33 +++++ ...ofile_referred_by_referral.cpython-311.pyc | Bin 0 -> 2391 bytes core/models.py | 16 +++ core/templates/core/dashboard.html | 57 +++++++-- core/templates/core/login.html | 3 +- core/templates/core/resend_activation.html | 45 +++++++ core/urls.py | 1 + core/views.py | 119 +++++++++++++++--- 23 files changed, 318 insertions(+), 38 deletions(-) create mode 100644 core/__pycache__/middleware.cpython-311.pyc create mode 100644 core/management/__init__.py create mode 100644 core/management/__pycache__/__init__.cpython-311.pyc create mode 100644 core/management/commands/__init__.py create mode 100644 core/management/commands/__pycache__/__init__.cpython-311.pyc create mode 100644 core/management/commands/__pycache__/resend_activation.cpython-311.pyc create mode 100644 core/management/commands/resend_activation.py create mode 100644 core/middleware.py create mode 100644 core/migrations/0002_profile_referred_by_referral.py create mode 100644 core/migrations/__pycache__/0002_profile_referred_by_referral.cpython-311.pyc create mode 100644 core/templates/core/resend_activation.html diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index d74a112455a3ca43091010a638254dae51a3a31d..280db1bb83b267a150d2fc7ea0658aae79d95ca1 100644 GIT binary patch delta 1057 zcmZ`!O-~b16rI^UhuO{O~ew7)DQ#XS@9> z{q-qhN1Go8KcaDH!|f>w+E);^kZ2*%3RY|b8}5J((lLRZ_|6rSM$q-5DTS~dx^X8s zphp_f%u4rB&%=Juzt&LHJ7U{ULy7cl^}BivhvCTnISjy2!Z-M$IgM}^41tqWL*P=q zTlJnaN;Tnb@L~t}a1WSpFR?!G<3pkj0*eNU!u=#WwrIFSQ6w3zw@5r*k0}}-ZV)lj zC=wqbh=B&wD4dYuv_pDAj~JLe(b8{vT6gM>dLVW_YvNL;cM4Yn!8n}RTdQA>-Ej61 z)qpxDy7!}&J#}p3g9+(N$bqcSk`%Pz>sVz=h^`PNDOeoI>Yv8ATTjUYJQv@v*4nH-15Hr7hzhG%Rhl zZgtqU!F9P;t|kRxCK=&^VUCx+v$8|cbLyt5fKJcncN=vb(i+1ZGKaaT;M`0?B~Pko zMVnj5r1RNe{g9CK(}eYTk|JU`ZRMglirfdNuCr^Dt!8X7YiiW4D;R6-dLwDjgrW&#EM2E#rUiuR(pWG|my)mBYD+2pG-ewp!;p}DKrUvieGSIt$k zM>cy3tsB%SmNPmwQOEibKkCaac^Yzavf%getpyAj|3 delta 943 zcmZ8eO-~w86rIP2723ha2q=hR{X$UWOAtY8u|jQx(5hYZbzy2QQmio+OczZPS#;4Q zW=QJ7w8kdZq+Qq$6LAFcbb=O_fH>jy_lY7p6_nmj{oy-qMd5HVWacYLf zHhfCQwm2jD_04*OW>O5a>>;9~tfQ<4Ju!fRv_dQGXrYawaSx>tn06Xch_pdFF@hO7 z6ikv`sT#_r&?&4Vi(pkuA+@lgux$41D;VYr&5M9u=%XI}J2%pZn7|4H^kfBF7420V zOrul-X@?RDtvLO@<-2Y2}hD%I-_{$edzH_w|jI=t^Et*DgVN zsHFG0jH*V!SF74bba9#&LHg?Se&8(97w#70x97u7E%wmNYL#%s-q9VXr~ zb*ei_d#!4B@M`>ZhDqVD!7@9P%8rUB>Yu2wlMc<;ieGO%LS}U&9)1v8oJq_tR4(g> ziMfRe>kEtujeNwUTzlGN&o-= diff --git a/config/settings.py b/config/settings.py index 3137147..03554d8 100644 --- a/config/settings.py +++ b/config/settings.py @@ -43,6 +43,9 @@ CSRF_COOKIE_SECURE = True SESSION_COOKIE_SAMESITE = "None" CSRF_COOKIE_SAMESITE = "None" +# IMPORTANT for reverse proxy (Apache) to correctly identify HTTPS +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ @@ -65,6 +68,7 @@ MIDDLEWARE = [ 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', + 'core.middleware.ReferralMiddleware', # Disable X-Frame-Options middleware to allow Flatlogic preview iframes. # 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] @@ -156,13 +160,13 @@ STATICFILES_DIRS = [ ] # Email -EMAIL_HOST = os.getenv("EMAIL_HOST", "127.0.0.1") -EMAIL_PORT = int(os.getenv("EMAIL_PORT", "587")) -EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "") -EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "") -EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "true").lower() == "true" -EMAIL_USE_SSL = os.getenv("EMAIL_USE_SSL", "false").lower() == "true" -DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "no-reply@example.com") +EMAIL_HOST = os.getenv("SMTP_HOST", "127.0.0.1") +EMAIL_PORT = int(os.getenv("SMTP_PORT", "587")) +EMAIL_HOST_USER = os.getenv("SMTP_USER", "") +EMAIL_HOST_PASSWORD = os.getenv("SMTP_PASS", "") +EMAIL_USE_TLS = os.getenv("SMTP_SECURE", "tls").lower() == "tls" +EMAIL_USE_SSL = os.getenv("SMTP_SECURE", "tls").lower() == "ssl" +DEFAULT_FROM_EMAIL = os.getenv("MAIL_FROM", "no-reply@example.com") CONTACT_EMAIL_TO = [ item.strip() for item in os.getenv("CONTACT_EMAIL_TO", DEFAULT_FROM_EMAIL).split(",") @@ -186,4 +190,4 @@ if EMAIL_USE_SSL: DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' LOGIN_REDIRECT_URL = 'dashboard' -LOGOUT_REDIRECT_URL = 'home' +LOGOUT_REDIRECT_URL = 'home' \ No newline at end of file diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index 92a26331f9f613783c5745ddf414ad95b777f2b0..f707a2c7e29a445dbb1b94f432ee65be86cf28b3 100644 GIT binary patch delta 746 zcmZ`$L2DC16rP#gY__etG+IL~5~_lAsbV2kL`0OD)F8BALk~F^wmVZynCzC>jbJYo z)T4*82R-yAM6^99{sT`QMLey4!eH?r3cfe7MWlVpd^7KRZ}{GP`_%0h+*gh>OdvjV zUbfyCo9;N3n^TqZjLZ{mJs~_VXgiM$7(IiKZCHb7*xX(s-q1Vsn9|qpe^5%TB9zo< z`kT#IC-7KI%f?4qNPiip+yW4unVGSWtLkJrZI-MF>?x&-raz7C9<6(hI*Q~mgfRrv z%Z4i*za^_JA-O7~U(L%?TB(FJr`ZgoAIE`^vf1>gIt^@00dA;?^h)mYh8_i95y09H z`|s)dG8~>0K^l~k24h4RZF8@SQ=RAbbH)vBZn2$Q4-~dhMaPcSW01odM8o2FFvrfu z?uw0Ag#2cZv>y0LD+;TT>eSbdtipC7h{8lga(%QCCPGaOZu;Aqna!Z}Tn*+;g;ac}xqR@Laj-b8JzI&J=ARpfYNEdU*m&wmr-lq5Fo2IL_zk2`V8I(oi`LS`%*3~A u&Hsk^X6e{c%95xJGD?#%M3c8j5F{uBB7{K%GmyB&<(iwAnd6q3nv+r_ zFqx6nOdaeBS&$rWe0*|FVsUYDYEDjkye4CjB2cuL5lA=`NluPqy~G1{COc5TPm_J} xLH1ykB5@$62rLY8!Y>Y+-29Z%oK(9aEg%=9wYXriGe;I5gCL{G2M_^P3;^+MLC^pI diff --git a/core/__pycache__/middleware.cpython-311.pyc b/core/__pycache__/middleware.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63574deb45443890c45f3f5bef5a97f9c67350fb GIT binary patch literal 955 zcmZ`%&x_MQ6n-;Fx7pfdWg}Ks4@Fimhc>Vv9$Z{KEInC41)-OrW~SOTZMu_&Rf({N zJ@nv7gnBFpitImQrC`ZP@E@qu)1G`YF$Jx@WZt~IAK!cPO+K!z6#(US>wUXM0KTh_ zOw8ZLyo|;WDAa(%F>vyM0-WGVr2(8i0LSn$huRTK%X|~JAqZ#?4h_o5F*FQLQJdVr zDl^%*FIqy%rhm81xi1czQg{n&ELOIGYkp26#UuuXRw^72gVY_86rYGblOpT|-B36* zF+<_EWDe6pg-|Y^Zo5x|j&P&LQMVtt;<@mmejwe$Kt2n5O;5O9AcfmmOi}H4a|vk@w0yC=z~8_qfVkv6QePNnCz<*Eu)OH%%OK= z8~rDX*ee=SS%@iCE?DAZ2XkW>DdZ)I|3)xk#XPMih7>Kl0i(9}Ffow7Bp-?}YzJM< zn`6oq1WWQ#97H1QCl;nNFW^F1Dbb<&N6IKNuTqgNuj%{$r7I2UOSyyjNSD3&_U3T+ z&F<*>*mfqiGq9#->=}^N>hx-5Pb~c4cB$Fm1gwSeL7a zQg?EyaktqKj3oueIsuP-WfvJch?;&Hkrw944OE{%*Xfa^qD(8raJY@?Xz|P_$vHSo zrdC#R7iO@mQaFW#e`1PwUcsvAbeB_WCmrgRETK=gx`D>bAcV|lfgtR%`k{P3{}Ir@ FzW{hS(uM#4 literal 0 HcmV?d00001 diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 63509f2b8aa9837666f3ec548f4cd997e4b2fbb3..d91fb3a99449de72641a7512b46ad4f53877b11e 100644 GIT binary patch delta 1877 zcmZ`(O>7fK6yCAd-i_m3$M!0Oriolg0`3oO38EiTjs=87a*E0@|Ha42I zQ!uFZfkO@)678V}R4Jk;B_fq9l|yfBFI6v<*3urLm3raSVYe3 z@Bq5sX!k8)6T}DY2_X~xU)|_Sx(W@=3Tb8i?}>=ivhd^|>m6d&(AiRV+bR{|3vWxC zA#erVZ=S2*lF}YidAn ziE+8oMPAAm4CYE#ePPQA2TqQ;Drb4ddDbxXg>;4`BBv@dvrkXI~A9O>&v1m;RRrY3{M|}~Pp1+G*G8Pg!Hvnr)Wc8y7_t{K zcHz36s5TQ-CsD1Rx8}l&2QXA`V-AE)>o6N3f_03XvL;F2KoBY24(%aS_UP)e?vVc1 zBEH-FgmSz~fWjb<9JGEU{k85rbSmC1%)Zw=FVPokHltDnD34JUIF7mUr52a2 zYO-Qk?@-I}R=5eya;&fdiR>)QwfF{-YjEHMsQIL0B|<(`+$UXSMOr8>ri1AK|InMc z*qRBr5wnuZG1JUf3^y{{k<$TJ=IrY%r;K7;zLXAn7J1S$lDkqWz*erIufXBw-gBL* zhZVddkR;*BQGjm+N~~I7e=TrRSn6EeI|2=Uhu2S>sjBT9r1HG9oT|#H!>3D>ok%?+ z>2wf-!g7aSADHvXj_(J_E1HyFG+1BNp0=GMx^9-au49Fo2LbBBPOLV+A>LD*)NwmD z55HeU=hP)%M3m<_*3V43hrb2?{0PF^2zU@aiXbAqi*OFWRa$XTy<$Recvk;Tgw1lc zTrv3=&b*JR(OhY{cGX~IATnF#810teIrPIHM@@uY=;jVUz4PtFQmZRYEVZFEdLItj zGhf;(C)NF0E zsf{|?D9kDYs|$`Ya6i%bd{+=ZBH=x#N{R63)=6pb%mp~F zD;J88wqg!myBud($P|AAlYvQ3LGu#jCjB)p<9h2=*dHIdnkh`siZ zgO@NM;#G`Ddk_S@h<|}M@iNEWyeW9{qVLVNN+~{=$D8+keBXO-=h)kp|Jw8F1j1Q+ z+52Wp{5JhKS>$)P%}s7>JJSjwTky(YwM{aGa%-I_+SBBO!`tIJJRbh_(3=by>{V_=?t8Y5tp)q+(!x>ap_6;#idl^^u z!?FnEdgWqM;1&T@HsfhL%8EdKt@LRiSBy34y?btKP&y}DW~Y4^c;Y0aC+@D&={$bK zXXH(@u-Yinl%LJ3tyw5lZS{UEuJm}u!~*=~HS5fUBT#4o`+#m!=!}_sQ&6S z@f`bAmH?lZ1Fusa$dfO;d)?#EROa;|B!u>bQPcvSxOqbiF20cWKopBJq$s2)u2EXX z1T-6nAs|H=q+A84To5J%QLfLBqKaX*+8VZH%s}lx3<2rfQ9>!|!3>%jlP57Hs|gjQ z7N_Q==q4tYWR@kCWaj7T-x7p~#UsQfe_{H>$h-JCGb0-)Sc-%uzhx1lNEP)qU0oiWzOQ6Y{sZ)&633nlfq$yNQ!8RSc>=>iDfK6gMb)nkR;F` zbE#C8EIybJ4g;hy+#mzgYAFlU3iA#w=gXzYuTfaW1hf~3q4p{QH3upIH4DImaM-Ji zVXw*>wq?vf!+;n9(z&7pQ&fW)G}R_=V@jUv!2FSsXR#3rBO4D;zDRJg1#2;*(Bw6& ofs>io&T^>$1wjE>d~5PewvNqJ?B0z0x{~}%4cs7Dqy&@#0P##e2><{9 diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index e8747cf316fa06945ba6f26b00db2076c84ee08f..40425921a909f20d086dfaf05cf5c7c68b5222fc 100644 GIT binary patch literal 9237 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 delta 2223 zcmZuyOKcNI7@pZ(?>b)FvGZEn*(5fB7&k8-;gv!Ih{T~Kfhr)XaEOd&NgQl1o!tOJ z4k;3q(h7;k6iQWEMXHphqA#hJNbP|`p_d*KWQw~|Bu=f6dN2aimQ!aoI3&g0+-jmKjA`hTFC?k0}vn=a;Jlt&|t{! zd(yQTbx?(X1DZHZO>Z)Ln+EHU_dFP^N97b7kdIwS1LQk*1NmF@*FC0VkYtJ_l4*yh?eR&T1jJ4< zBqq;5`tOj`9xO0Vluq37S9&=M^L>+RjjO>oJTLlZ|#>X}T+B){`V z9GHbeI)!?V<r*r2(X$_|HZd1v?xLGsoH)waX|YK{dq zy_nI=5gp-5#%Ulw%Jpu{$_bY&i~vP>-K=#9#@aV-&UG> zIQ|ajp#9`hHd7^$HoKbtfaGHJZ`?UVQZ;Kiyq{dH*+QCq@05!ys^ zmVZTaS)C$bWeyLlsO)7`4A6_JC4D|8jF8WLsvML0V-UB~wGNW*;9gimE(RkJc5B5d zu$%s{mx3i6qBor-=|@Mkq-o#{-j-|CX@Da*mkBX6@SmYsu1#)6l6*;%+=IRHac5dZY z0CjUKKay>XjPS6n2$sMVfn7zg1U=9T!$q(JVZix{91nXTv+0G8c*;0m|D5IGpjrg4 Hg6;f&I1cJw diff --git a/core/forms.py b/core/forms.py index be81d44..6d4e283 100644 --- a/core/forms.py +++ b/core/forms.py @@ -20,3 +20,6 @@ class SignupForm(UserCreationForm): if commit: user.save() return user + +class ResendActivationForm(forms.Form): + email = forms.EmailField(required=True, widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'Enter your email'})) \ No newline at end of file diff --git a/core/management/__init__.py b/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/__pycache__/__init__.cpython-311.pyc b/core/management/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..826e230199e2a0db1581ecb0d5d715294b30ae4d GIT binary patch literal 168 zcmZ3^%ge<81gu$$GePuY5CH>>P{wCAAY(d13PUi1CZpd>P{wCAAY(d13PUi1CZpdlIY~;;_lhPbtkwwJTx;8Va(um>)=dU}j`w{J;PsikN|701(|P<^TWy literal 0 HcmV?d00001 diff --git a/core/management/commands/__pycache__/resend_activation.cpython-311.pyc b/core/management/commands/__pycache__/resend_activation.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7fa2ad6636ea2c57a974b83f13d95c545bd6290 GIT binary patch literal 3680 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 bo_{Ym$p?GfyX0xajxQXtg`@YZI$r$?VljBB literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 3b991e3..b53237a 100644 --- a/core/models.py +++ b/core/models.py @@ -7,6 +7,7 @@ import uuid class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') referral_code = models.CharField(max_length=20, unique=True, blank=True) + referred_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='referrals_made') points = models.IntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True) @@ -18,6 +19,21 @@ class Profile(models.Model): self.referral_code = str(uuid.uuid4()).replace('-', '')[:8].upper() super().save(*args, **kwargs) +class Referral(models.Model): + referrer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_referrals') + referred_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='received_referrals', null=True, blank=True) + referral_code = models.CharField(max_length=20) + status = models.CharField(max_length=20, default='pending', choices=[ + ('pending', 'Pending'), + ('successful', 'Successful'), + ('rewarded', 'Rewarded'), + ]) + created_at = models.DateTimeField(auto_now_add=True) + completed_at = models.DateTimeField(null=True, blank=True) + + def __str__(self): + return f"Referral from {self.referrer.username} (Code: {self.referral_code})" + @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): if created: diff --git a/core/templates/core/dashboard.html b/core/templates/core/dashboard.html index 874c0c3..a9e8f0c 100644 --- a/core/templates/core/dashboard.html +++ b/core/templates/core/dashboard.html @@ -25,9 +25,9 @@

Total Points

{{ profile.points }}

-

Points to next reward: 100

+

Total successful referrals: {{ referrals.count }}

-
+
@@ -36,16 +36,53 @@

Your Referral Link

-

Share this code with your friends and earn points when they sign up!

+

Share this link with your friends and earn points when they sign up and activate their account!

- - + +
- - + Share on Twitter + Share on WhatsApp +
+
+
+ + +
+

Successful Referrals

+
+
+ + + + + + + + + + + {% for referral in referrals %} + + + + + + + {% empty %} + + + + {% endfor %} + +
UsernameDate JoinedStatusReward
{{ referral.referred_user.username }}{{ referral.completed_at|date:"M d, Y" }} + {{ referral.get_status_display }} + +10 Points
+ No referrals yet. Share your link to start earning rewards! +
@@ -96,8 +133,8 @@ -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/login.html b/core/templates/core/login.html index 34416b3..aebd6bf 100644 --- a/core/templates/core/login.html +++ b/core/templates/core/login.html @@ -32,7 +32,8 @@ -
+ diff --git a/core/templates/core/resend_activation.html b/core/templates/core/resend_activation.html new file mode 100644 index 0000000..d3f38f4 --- /dev/null +++ b/core/templates/core/resend_activation.html @@ -0,0 +1,45 @@ +{% extends 'base.html' %} + +{% block title %}Resend Activation - Referral Rewards{% endblock %} + +{% block content %} +
+
+
+
+
+

Resend Activation

+

Enter your email to receive a new activation link.

+
+ +
+ {% csrf_token %} + {% if form.non_field_errors %} +
+ {% for error in form.non_field_errors %} + {{ error }} + {% endfor %} +
+ {% endif %} + +
+ + {{ form.email }} + {% if form.email.errors %} +
+ {{ form.email.errors }} +
+ {% endif %} +
+ + + + +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 3578e5a..6943a65 100644 --- a/core/urls.py +++ b/core/urls.py @@ -6,6 +6,7 @@ urlpatterns = [ path("", views.home, name="home"), path("signup/", views.signup, name="signup"), path("activate///", views.activate, name="activate"), + path("resend-activation/", views.resend_activation, name="resend_activation"), 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"), diff --git a/core/views.py b/core/views.py index 1066a46..9a745bb 100644 --- a/core/views.py +++ b/core/views.py @@ -7,13 +7,30 @@ 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 django.utils import timezone -from .models import Profile -from .forms import SignupForm +from .models import Profile, Referral +from .forms import SignupForm, ResendActivationForm from .tokens import account_activation_token User = get_user_model() +def send_activation_email(request, user): + 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 = user.email + email = EmailMessage( + mail_subject, message, to=[to_email] + ) + return email.send() + def home(request): if request.user.is_authenticated: return redirect('dashboard') @@ -29,22 +46,30 @@ def signup(request): user.is_active = False user.save() + # Referral logic + ref_code = request.session.get('ref') + if ref_code: + try: + referrer_profile = Profile.objects.get(referral_code=ref_code) + referrer = referrer_profile.user + + # Update user's profile with referrer + user.profile.referred_by = referrer + user.profile.save() + + # Create Referral record + Referral.objects.create( + referrer=referrer, + referred_user=user, + referral_code=ref_code, + status='pending' + ) + except Profile.DoesNotExist: + pass # Invalid referral code, just ignore + # 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() + send_activation_email(request, user) 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.') @@ -54,6 +79,34 @@ def signup(request): form = SignupForm() return render(request, 'core/signup.html', {'form': form}) +def resend_activation(request): + if request.user.is_authenticated: + return redirect('dashboard') + + if request.method == 'POST': + form = ResendActivationForm(request.POST) + if form.is_valid(): + email_addr = form.cleaned_data.get('email') + try: + user = User.objects.get(email=email_addr) + if not user.is_active: + send_activation_email(request, user) + messages.success(request, 'A new activation email has been sent. Please check your inbox.') + return redirect('login') + else: + messages.info(request, 'This account is already active. Please log in.') + return redirect('login') + except User.DoesNotExist: + # Security: Don't reveal if email exists, just say it's sent if it's inactive + messages.success(request, 'If an inactive account with that email exists, an activation email has been sent.') + return redirect('login') + except Exception as e: + messages.error(request, f'Error sending email: {str(e)}. Please contact support.') + else: + form = ResendActivationForm() + + return render(request, 'core/resend_activation.html', {'form': form}) + def activate(request, uidb64, token): try: uid = force_str(urlsafe_base64_decode(uidb64)) @@ -63,6 +116,21 @@ def activate(request, uidb64, token): if user is not None and account_activation_token.check_token(user, token): user.is_active = True user.save() + + # Complete referral if exists + referral = Referral.objects.filter(referred_user=user, status='pending').first() + if referral: + referral.status = 'successful' + referral.completed_at = timezone.now() + referral.save() + + # Reward referrer (e.g. add 10 points) + referrer_profile = referral.referrer.profile + referrer_profile.points += 10 + referrer_profile.save() + + messages.info(request, f'You were successfully referred by {referral.referrer.username}!') + login(request, user) messages.success(request, 'Thank you for your email confirmation. Now you can enjoy our services.') return redirect('dashboard') @@ -72,10 +140,23 @@ def activate(request, uidb64, token): @login_required def dashboard(request): - # 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}) + + # Get successful referrals + referrals = Referral.objects.filter(referrer=request.user, status__in=['successful', 'rewarded']).order_by('-completed_at') + + current_site = get_current_site(request) + domain = current_site.domain + protocol = 'https' if request.is_secure() else 'http' + referral_link = f"{protocol}://{domain}/?ref={profile.referral_code}" + + context = { + 'profile': profile, + 'referrals': referrals, + 'referral_link': referral_link, + } + return render(request, 'core/dashboard.html', context) def logout_view(request): logout(request) - return redirect('home') + return redirect('home') \ No newline at end of file