diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index d74a112..280db1b 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ 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 92a2633..f707a2c 100644 Binary files a/core/__pycache__/forms.cpython-311.pyc and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/middleware.cpython-311.pyc b/core/__pycache__/middleware.cpython-311.pyc new file mode 100644 index 0000000..63574de Binary files /dev/null and b/core/__pycache__/middleware.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 63509f2..d91fb3a 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 46a6e0e..fa589af 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 e8747cf..4042592 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 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 0000000..826e230 Binary files /dev/null and b/core/management/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/commands/__pycache__/__init__.cpython-311.pyc b/core/management/commands/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..66a7108 Binary files /dev/null and b/core/management/commands/__pycache__/__init__.cpython-311.pyc differ 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 0000000..c7fa2ad Binary files /dev/null and b/core/management/commands/__pycache__/resend_activation.cpython-311.pyc differ diff --git a/core/management/commands/resend_activation.py b/core/management/commands/resend_activation.py new file mode 100644 index 0000000..88ac0ed --- /dev/null +++ b/core/management/commands/resend_activation.py @@ -0,0 +1,48 @@ +import os +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode +from django.template.loader import render_to_string +from django.core.mail import EmailMessage +from django.conf import settings +from core.tokens import account_activation_token + +User = get_user_model() + +class Command(BaseCommand): + help = 'Resend activation email to a specific user' + + def add_arguments(self, parser): + parser.add_argument('username', type=str) + parser.add_argument('--domain', type=str, default=os.getenv("HOST_FQDN", "localhost:8000")) + + def handle(self, *args, **args_options): + username = args_options['username'] + domain = args_options['domain'] + + try: + user = User.objects.get(username=username) + if user.is_active: + self.stdout.write(self.style.WARNING(f"User '{username}' is already active.")) + return + + mail_subject = 'Activate your Referral Rewards account.' + message = render_to_string('core/emails/activation_email.html', { + 'user': user, + 'domain': domain, + 'protocol': 'https', # Defaulting to https for Flatlogic + 'uid': urlsafe_base64_encode(force_bytes(user.pk)), + 'token': account_activation_token.make_token(user), + }) + + email = EmailMessage( + mail_subject, message, to=[user.email] + ) + email.send() + self.stdout.write(self.style.SUCCESS(f"Successfully sent activation email to '{username}' ({user.email}).")) + + except User.DoesNotExist: + self.stdout.write(self.style.ERROR(f"User '{username}' does not exist.")) + except Exception as e: + self.stdout.write(self.style.ERROR(f"Error sending email: {str(e)}")) diff --git a/core/middleware.py b/core/middleware.py new file mode 100644 index 0000000..3681baa --- /dev/null +++ b/core/middleware.py @@ -0,0 +1,11 @@ +class ReferralMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + ref_code = request.GET.get('ref') + if ref_code: + request.session['ref'] = ref_code + + response = self.get_response(request) + return response diff --git a/core/migrations/0002_profile_referred_by_referral.py b/core/migrations/0002_profile_referred_by_referral.py new file mode 100644 index 0000000..c0e1f7e --- /dev/null +++ b/core/migrations/0002_profile_referred_by_referral.py @@ -0,0 +1,33 @@ +# Generated by Django 5.2.7 on 2026-02-28 22:03 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='referred_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='referrals_made', to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='Referral', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('referral_code', models.CharField(max_length=20)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('successful', 'Successful'), ('rewarded', 'Rewarded')], default='pending', max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('completed_at', models.DateTimeField(blank=True, null=True)), + ('referred_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='received_referrals', to=settings.AUTH_USER_MODEL)), + ('referrer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_referrals', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/core/migrations/__pycache__/0002_profile_referred_by_referral.cpython-311.pyc b/core/migrations/__pycache__/0002_profile_referred_by_referral.cpython-311.pyc new file mode 100644 index 0000000..7700c5f Binary files /dev/null and b/core/migrations/__pycache__/0002_profile_referred_by_referral.cpython-311.pyc differ 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
Points to next reward: 100
+Total successful referrals: {{ referrals.count }}
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!
| Username | +Date Joined | +Status | +Reward | +
|---|---|---|---|
| {{ 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! + | +|||
Enter your email to receive a new activation link.
+