diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..726a88a258b658d7d264dbb573fa16483739f61b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +db.sqlite3 filter=lfs diff=lfs merge=lfs -text +static/images/WhatsApp_Image_2025-08-16_at_10.06.25_PM.jpeg filter=lfs diff=lfs merge=lfs -text diff --git a/Accounts/Home/__init__.py b/Accounts/Home/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Accounts/Home/__pycache__/__init__.cpython-313.pyc b/Accounts/Home/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6203e34665b9bc119cfad063ad4664faed3f4ce3 Binary files /dev/null and b/Accounts/Home/__pycache__/__init__.cpython-313.pyc differ diff --git a/Accounts/Home/__pycache__/admin.cpython-313.pyc b/Accounts/Home/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4c18489c0f29e7cc84f39eedc291702458a6a2e Binary files /dev/null and b/Accounts/Home/__pycache__/admin.cpython-313.pyc differ diff --git a/Accounts/Home/__pycache__/apps.cpython-313.pyc b/Accounts/Home/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8da37f7bd0c6e0498c553b99c3085511b997d007 Binary files /dev/null and b/Accounts/Home/__pycache__/apps.cpython-313.pyc differ diff --git a/Accounts/Home/__pycache__/models.cpython-313.pyc b/Accounts/Home/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..421da762a0030321445c3e51a86715e57cf6b4bc Binary files /dev/null and b/Accounts/Home/__pycache__/models.cpython-313.pyc differ diff --git a/Accounts/Home/__pycache__/urls.cpython-313.pyc b/Accounts/Home/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d4618fc22656e740b36b6f13003c5228bc8020d Binary files /dev/null and b/Accounts/Home/__pycache__/urls.cpython-313.pyc differ diff --git a/Accounts/Home/__pycache__/views.cpython-313.pyc b/Accounts/Home/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02acdcde8cd261dab883c1d6422e3e1b17652520 Binary files /dev/null and b/Accounts/Home/__pycache__/views.cpython-313.pyc differ diff --git a/Accounts/Home/admin.py b/Accounts/Home/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..ea5d68b7c457cb7f92da9c00a5c4df77ace36cef --- /dev/null +++ b/Accounts/Home/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Accounts/Home/apps.py b/Accounts/Home/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..0d010287ef2b6cf03b2ce1688e5e89280510cd13 --- /dev/null +++ b/Accounts/Home/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HomeConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Home' diff --git a/Accounts/Home/migrations/__init__.py b/Accounts/Home/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Accounts/Home/migrations/__pycache__/__init__.cpython-313.pyc b/Accounts/Home/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdf4ebd5adfeca79c2ba4f7b40c62d9b08e95eda Binary files /dev/null and b/Accounts/Home/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/Accounts/Home/models.py b/Accounts/Home/models.py new file mode 100644 index 0000000000000000000000000000000000000000..fd18c6eac0dc9ffbdf025c31d136901350a0d9f2 --- /dev/null +++ b/Accounts/Home/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/Accounts/Home/templates/index.html b/Accounts/Home/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..e62655476d276575cb7900b76fded628d2987c7c --- /dev/null +++ b/Accounts/Home/templates/index.html @@ -0,0 +1,247 @@ + + + + + + MysteryBox | Home + + + + + + + +
+
+ + + + 🎁 MysteryBox + + + + + + + + +
+
+ + + +
+
+ +
+ +
+ +

+ Unbox the Mystery. Discover the Surprise! +

+

+ Branded products at unbelievable prices β€” delivered to your door. +

+ + Shop Now + +
+
+ + + +
+
+

β‚Ή299 Box

+ Buy Now +
+
+

β‚Ή499 Box

+ Buy Now +
+
+

β‚Ή999 Box

+ Buy Now +
+
+
+ + +
+ + +
+ Box +
+

Box β‚Ή299

+

Small Mystery Surprise

+ +
+
+ +
+ Box +
+

Box β‚Ή499

+

Medium Mystery Box

+ +
+
+ +
+ Box +
+

Box β‚Ή999

+

Premium Mystery Offer

+ +
+
+ +
+ Box +
+

Box β‚Ή1499

+

Ultra Luxury Box

+ +
+
+ + + +
+
+ +
+
+

Why Choose Us

+
+
+
πŸ’°
+

Affordable Prices

+
+
+
πŸŽ‰
+

Surprise & Fun

+
+
+
🌱
+

Eco-Friendly

+
+
+
⭐
+

Trusted Sellers

+
+
+
+
+ + +
+
+

Customer Reviews

+
+
+ 😊 Aman +

β€œLoved the mystery box! Amazing quality at low cost.”

+

⭐⭐⭐⭐⭐

+
+
+ 😎 Lucky +

β€œGreat surprise and value. Will order again!”

+

⭐⭐⭐⭐

+
+
+
+
+ + + + + + + + + + + diff --git a/Accounts/Home/tests.py b/Accounts/Home/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..de8bdc00eb2fed53494a534d48e400faa830dbd9 --- /dev/null +++ b/Accounts/Home/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Accounts/Home/urls.py b/Accounts/Home/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..69b2cd4aa80de859dc1c3976654d120ebe82a0fd --- /dev/null +++ b/Accounts/Home/urls.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from django.urls import path +from Home import views +from django.contrib.auth import views as auth_views +from . import * + +urlpatterns = [ + path("", views.index , name="Home"), + path("reward", views.all_boxes , name="box"), + +] \ No newline at end of file diff --git a/Accounts/Home/views.py b/Accounts/Home/views.py new file mode 100644 index 0000000000000000000000000000000000000000..dae8ebf52d27d5160eb17a072a368d61392c2c38 --- /dev/null +++ b/Accounts/Home/views.py @@ -0,0 +1,12 @@ +from django.shortcuts import render +from django.shortcuts import render , HttpResponse , redirect +# Create your views here. + + + +def index(request): + return render(request, 'Home/templates/index.html' ) + +def all_boxes(request): + + return render(request, "Boxes/templates/spin_wheel.html") \ No newline at end of file diff --git a/Accounts/__init__.py b/Accounts/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Accounts/__pycache__/__init__.cpython-313.pyc b/Accounts/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ab7d87c944ca4f098f3a022a74cb6fc1ed8cafa Binary files /dev/null and b/Accounts/__pycache__/__init__.cpython-313.pyc differ diff --git a/Accounts/__pycache__/admin.cpython-313.pyc b/Accounts/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3faa5284ec04e0a06637bec7665e51228bec551 Binary files /dev/null and b/Accounts/__pycache__/admin.cpython-313.pyc differ diff --git a/Accounts/__pycache__/apps.cpython-313.pyc b/Accounts/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16befdc6ceecd39107191e7aa2e87026b6cd1a1c Binary files /dev/null and b/Accounts/__pycache__/apps.cpython-313.pyc differ diff --git a/Accounts/__pycache__/models.cpython-313.pyc b/Accounts/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d14e10bc932d4b4ff3bf5d4ff8e0a580846db697 Binary files /dev/null and b/Accounts/__pycache__/models.cpython-313.pyc differ diff --git a/Accounts/__pycache__/urls.cpython-313.pyc b/Accounts/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..092a271998b8dcbfe7817dec000c5ea79e478c62 Binary files /dev/null and b/Accounts/__pycache__/urls.cpython-313.pyc differ diff --git a/Accounts/__pycache__/views.cpython-313.pyc b/Accounts/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee98784cb94fb612610dfd4aec51b948f7148217 Binary files /dev/null and b/Accounts/__pycache__/views.cpython-313.pyc differ diff --git a/Accounts/admin.py b/Accounts/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..2559c96ecb37dec1d278222bc81a6eee7cf5a52a --- /dev/null +++ b/Accounts/admin.py @@ -0,0 +1,36 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from .models import User, Profile + + +class UserAdmin(BaseUserAdmin): + + list_display = ('email', 'first_name', 'last_name', 'is_staff', 'is_active', 'date_joined') + list_filter = ('is_staff', 'is_active', 'is_superuser') + search_fields = ('email', 'first_name', 'last_name') + ordering = ('-date_joined',) + + + fieldsets = ( + (None, {'fields': ('email', 'password')}), + ('Personal Info', {'fields': ('first_name', 'last_name')}), + ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), + ('Important dates', {'fields': ('last_login', 'date_joined')}), + ) + + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'password1', 'password2', 'is_active', 'is_staff')} + ), + ) + + +class ProfileAdmin(admin.ModelAdmin): + list_display = ('user', 'phone', 'wallet_balance') + search_fields = ('user__email', 'phone') + list_filter = ('wallet_balance',) + + +admin.site.register(User, UserAdmin) +admin.site.register(Profile, ProfileAdmin) diff --git a/Accounts/apps.py b/Accounts/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..e6e620c37f4e010864194c5850913141c57e678c --- /dev/null +++ b/Accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Accounts' diff --git a/Accounts/migrations/0001_initial.py b/Accounts/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..9b6d057d8f09ea1ee02603296f31bd007c43fff7 --- /dev/null +++ b/Accounts/migrations/0001_initial.py @@ -0,0 +1,49 @@ +# Generated by Django 5.2.4 on 2025-08-18 10:37 + +import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('email', models.EmailField(max_length=255, unique=True)), + ('first_name', models.CharField(blank=True, max_length=50, null=True)), + ('last_name', models.CharField(blank=True, max_length=50, null=True)), + ('is_active', models.BooleanField(default=True)), + ('is_staff', models.BooleanField(default=False)), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('phone', models.CharField(blank=True, max_length=15, null=True)), + ('address', models.TextField(blank=True, null=True)), + ('wallet_balance', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)), + ('preferences', models.JSONField(blank=True, null=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/Accounts/migrations/__init__.py b/Accounts/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Accounts/migrations/__pycache__/0001_initial.cpython-313.pyc b/Accounts/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07a5d16432a61670ba24c3fae375d7a5aa10ae61 Binary files /dev/null and b/Accounts/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/Accounts/migrations/__pycache__/__init__.cpython-313.pyc b/Accounts/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0df544f3202c766214f758898f7847e13a0f3895 Binary files /dev/null and b/Accounts/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/Accounts/models.py b/Accounts/models.py new file mode 100644 index 0000000000000000000000000000000000000000..5857513480d63e1189c0246ac5a7c5bdec50caeb --- /dev/null +++ b/Accounts/models.py @@ -0,0 +1,58 @@ +from django.db import models +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin +from django.utils import timezone + + +# ---------------- CUSTOM USER MANAGER ---------------- +class CustomUserManager(BaseUserManager): + def create_user(self, email, password=None, **extra_fields): + if not email: + raise ValueError("The Email field must be set") + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None, **extra_fields): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser must have is_staff=True.") + if extra_fields.get("is_superuser") is not True: + raise ValueError("Superuser must have is_superuser=True.") + + return self.create_user(email, password, **extra_fields) + + +# ---------------- CUSTOM USER MODEL ---------------- +class User(AbstractBaseUser, PermissionsMixin): + email = models.EmailField(unique=True, max_length=255) + first_name = models.CharField(max_length=50, blank=True, null=True) + last_name = models.CharField(max_length=50, blank=True, null=True) + + + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=False) + date_joined = models.DateTimeField(default=timezone.now) + + objects = CustomUserManager() + + USERNAME_FIELD = "email" + REQUIRED_FIELDS = [] + + def __str__(self): + return self.email + + +# ---------------- USER PROFILE MODEL ---------------- +class Profile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile") + phone = models.CharField(max_length=15, blank=True, null=True) + address = models.TextField(blank=True, null=True) + wallet_balance = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) + preferences = models.JSONField(blank=True, null=True) + + def __str__(self): + return f"Profile - {self.user.email}" diff --git a/Accounts/templates/login.html b/Accounts/templates/login.html new file mode 100644 index 0000000000000000000000000000000000000000..3ca53386abb9552ff07084ffd793586d618151e7 --- /dev/null +++ b/Accounts/templates/login.html @@ -0,0 +1,188 @@ + + + + + + MysteryBox - Login + + + + + + + +
+ + + + +
+
+
+

Login

+

Enter your credentials to continue your mystery journey

+
+ + +
+ {% csrf_token %} +
+ +
+ + +
+
+
+ +
+ + +
+
+
+ + Forgot Password? +
+ +
+ + +
+
+ OR +
+
+ + + + + +

+ Don't have an account? + Sign Up +

+
+
+
+ + + + + \ No newline at end of file diff --git a/Accounts/templates/signup.html b/Accounts/templates/signup.html new file mode 100644 index 0000000000000000000000000000000000000000..c80408614a4f241408546650b65c3f534e380b5b --- /dev/null +++ b/Accounts/templates/signup.html @@ -0,0 +1,211 @@ + + + + + + MysteryBox - Sign Up + + + + + + + +
+ + + + +
+
+
+

Sign Up

+

Fill in your details to start your mystery adventure

+
+ + +
+ {% csrf_token %} +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ +
+ + +
+
+ OR +
+
+ + + + + +

+ Already have an account? + Login +

+
+
+
+ + + + + \ No newline at end of file diff --git a/Accounts/tests.py b/Accounts/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..de8bdc00eb2fed53494a534d48e400faa830dbd9 --- /dev/null +++ b/Accounts/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Accounts/urls.py b/Accounts/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..785295d14dde9c20f1e71dc5dc83f889f65c519b --- /dev/null +++ b/Accounts/urls.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from django.urls import path +from Accounts import views +from django.contrib.auth import views as auth_views +from . import * + +urlpatterns = [ + path("login", views.login , name="login"), + path("signup", views.signup , name="signup"), + path("logout/", views.logout_view , name="logout"), + +] \ No newline at end of file diff --git a/Accounts/views.py b/Accounts/views.py new file mode 100644 index 0000000000000000000000000000000000000000..f5302c1b8545238e185f1298378f6d686c6dfb0f --- /dev/null +++ b/Accounts/views.py @@ -0,0 +1,58 @@ +from django.shortcuts import render , HttpResponse , redirect +from django.contrib.auth import login as log , authenticate , logout +from Accounts.models import User +from django.contrib import messages + +# Create your views here. + + + +def login(request): + if request.method == 'POST': + email = request.POST.get('email') + password = request.POST.get('password') + user = authenticate(request, email=email, password=password) + if user is not None: + log(request, user) + + return redirect('/') + else: + + return render(request, 'login.html', {'error_message': 'Invalid email or password'}) + else: + return render(request, 'Accounts/templates/login.html' ) + + + +def signup(request): + if request.method == "POST": + first_name = request.POST.get("first_name") + last_name = request.POST.get("last_name") + email = request.POST.get("email") + password1 = request.POST.get("password1") + password2 = request.POST.get("password2") + + # Validation + if password1 != password2: + messages.error(request, "Passwords do not match.") + return redirect("/Accounts/signup") + + if User.objects.filter(email=email).exists(): + messages.error(request, "Email already registered.") + return redirect("/Accounts/signup") + + + user = User.objects.create_user( + email=email, + first_name=first_name, + last_name=last_name, + password=password1, + ) + return redirect('/') + else: + return render(request, 'Accounts/templates/signup.html' ) + + +def logout_view(request): + logout(request) + return redirect('/') \ No newline at end of file diff --git a/Analytics/__init__.py b/Analytics/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Analytics/__pycache__/__init__.cpython-313.pyc b/Analytics/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fb94065fd611650f84014137c0f92189599bb8e Binary files /dev/null and b/Analytics/__pycache__/__init__.cpython-313.pyc differ diff --git a/Analytics/__pycache__/admin.cpython-313.pyc b/Analytics/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f51492ed326f4e032394049f9926698447323815 Binary files /dev/null and b/Analytics/__pycache__/admin.cpython-313.pyc differ diff --git a/Analytics/__pycache__/apps.cpython-313.pyc b/Analytics/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4eaaa0fa5de782529a80badaeff13a5aae919f1c Binary files /dev/null and b/Analytics/__pycache__/apps.cpython-313.pyc differ diff --git a/Analytics/__pycache__/models.cpython-313.pyc b/Analytics/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df8ab68f720466f5a8157df207f9369667a7a6dc Binary files /dev/null and b/Analytics/__pycache__/models.cpython-313.pyc differ diff --git a/Analytics/admin.py b/Analytics/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..9846ce35c725e4423f093a8d8f1c9df40f436d2c --- /dev/null +++ b/Analytics/admin.py @@ -0,0 +1,17 @@ +from django.contrib import admin +from .models import SalesReport, UserStats + + +class SalesReportAdmin(admin.ModelAdmin): + list_display = ('date', 'total_orders', 'total_revenue', 'top_box') + list_filter = ('date',) + search_fields = ('top_box__title',) + + +class UserStatsAdmin(admin.ModelAdmin): + list_display = ('user', 'total_spent', 'boxes_purchased', 'last_active') + search_fields = ('user__email',) + + +admin.site.register(SalesReport, SalesReportAdmin) +admin.site.register(UserStats, UserStatsAdmin) diff --git a/Analytics/apps.py b/Analytics/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..a2543656d9a47e56b29e3f277ea852f70bbf0ae5 --- /dev/null +++ b/Analytics/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AnalyticsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Analytics' diff --git a/Analytics/migrations/0001_initial.py b/Analytics/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..1a3f511aa32725956448a93092ebfbec18920d08 --- /dev/null +++ b/Analytics/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# Generated by Django 5.2.4 on 2025-08-18 10:42 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('Accounts', '0001_initial'), + ('Boxes', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='SalesReport', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('total_orders', models.IntegerField()), + ('total_revenue', models.DecimalField(decimal_places=2, max_digits=12)), + ('top_box', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='Boxes.mysterybox')), + ], + ), + migrations.CreateModel( + name='UserStats', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('total_spent', models.DecimalField(decimal_places=2, default=0.0, max_digits=12)), + ('boxes_purchased', models.IntegerField(default=0)), + ('last_active', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='Accounts.profile')), + ], + ), + ] diff --git a/Analytics/migrations/__init__.py b/Analytics/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Analytics/migrations/__pycache__/0001_initial.cpython-313.pyc b/Analytics/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3024f43e2f243cc3aba33619830a60f59e162f5a Binary files /dev/null and b/Analytics/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/Analytics/migrations/__pycache__/__init__.cpython-313.pyc b/Analytics/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..829fc0675f0beb7e6780d5187d56f7d64e55f556 Binary files /dev/null and b/Analytics/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/Analytics/models.py b/Analytics/models.py new file mode 100644 index 0000000000000000000000000000000000000000..e75a05cdb6545ec1d5fd0bb072a08b6c4ac6cfa0 --- /dev/null +++ b/Analytics/models.py @@ -0,0 +1,22 @@ +from django.db import models +from Boxes.models import MysteryBox +from Accounts.models import Profile + +class SalesReport(models.Model): + date = models.DateField() + total_orders = models.IntegerField() + total_revenue = models.DecimalField(max_digits=12, decimal_places=2) + top_box = models.ForeignKey(MysteryBox, on_delete=models.SET_NULL, null=True, blank=True) + + def __str__(self): + return f"Sales Report - {self.date}" + + +class UserStats(models.Model): + user = models.OneToOneField(Profile, on_delete=models.CASCADE) + total_spent = models.DecimalField(max_digits=12, decimal_places=2, default=0.00) + boxes_purchased = models.IntegerField(default=0) + last_active = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"Stats for {self.user.user.username}" diff --git a/Analytics/tests.py b/Analytics/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..de8bdc00eb2fed53494a534d48e400faa830dbd9 --- /dev/null +++ b/Analytics/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Analytics/urls.py b/Analytics/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Analytics/views.py b/Analytics/views.py new file mode 100644 index 0000000000000000000000000000000000000000..c60c790437030748012ed3b77ed8708956de9957 --- /dev/null +++ b/Analytics/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/Boxes/__init__.py b/Boxes/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Boxes/__pycache__/__init__.cpython-313.pyc b/Boxes/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c81f696a36a6a5f3c31a2732c1686bca3b7ed4b Binary files /dev/null and b/Boxes/__pycache__/__init__.cpython-313.pyc differ diff --git a/Boxes/__pycache__/admin.cpython-313.pyc b/Boxes/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a621e705ebfd7a9037593e1f3be05d1c641a90c Binary files /dev/null and b/Boxes/__pycache__/admin.cpython-313.pyc differ diff --git a/Boxes/__pycache__/apps.cpython-313.pyc b/Boxes/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4576f6ed59f6d26be02b52b5a726535c81752879 Binary files /dev/null and b/Boxes/__pycache__/apps.cpython-313.pyc differ diff --git a/Boxes/__pycache__/models.cpython-313.pyc b/Boxes/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19a3e3a8311d869dd9da62c0fef2aa4f67cc7b9e Binary files /dev/null and b/Boxes/__pycache__/models.cpython-313.pyc differ diff --git a/Boxes/__pycache__/urls.cpython-313.pyc b/Boxes/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c54d8943c76325f6acb900437c220e43b54b4e2d Binary files /dev/null and b/Boxes/__pycache__/urls.cpython-313.pyc differ diff --git a/Boxes/__pycache__/views.cpython-313.pyc b/Boxes/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b46d382bbbeab6ef90d8417fcd985d7fd397afe Binary files /dev/null and b/Boxes/__pycache__/views.cpython-313.pyc differ diff --git a/Boxes/admin.py b/Boxes/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..38c98326d38f87a497235b4c7799fe058fd25e05 --- /dev/null +++ b/Boxes/admin.py @@ -0,0 +1,18 @@ +from django.contrib import admin +from .models import Product, MysteryBox + + +class ProductAdmin(admin.ModelAdmin): + list_display = ('name', 'brand', 'category', 'original_price', 'condition', 'status') + list_filter = ('category', 'condition', 'status') + search_fields = ('name', 'brand') + + +class MysteryBoxAdmin(admin.ModelAdmin): + list_display = ('title', 'price', 'category', 'stock_quantity', 'is_featured') + list_filter = ('category', 'is_featured') + search_fields = ('title',) + + +admin.site.register(Product, ProductAdmin) +admin.site.register(MysteryBox, MysteryBoxAdmin) diff --git a/Boxes/apps.py b/Boxes/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..4d540fbdd94ef20bda2dd33f270faac69406064d --- /dev/null +++ b/Boxes/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BoxesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Boxes' diff --git a/Boxes/migrations/0001_initial.py b/Boxes/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..0e6f574b5e52ca4feb43252c4225ad94bd814f08 --- /dev/null +++ b/Boxes/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# Generated by Django 5.2.4 on 2025-08-18 10:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('brand', models.CharField(blank=True, max_length=100, null=True)), + ('category', models.CharField(max_length=100)), + ('original_price', models.DecimalField(decimal_places=2, max_digits=10)), + ('condition', models.CharField(choices=[('new', 'New'), ('returned', 'Returned'), ('like_new', 'Like New')], default='like_new', max_length=20)), + ('status', models.CharField(choices=[('available', 'Available'), ('in_box', 'In Box'), ('sold', 'Sold')], default='available', max_length=20)), + ('seller_name', models.CharField(blank=True, max_length=150, null=True)), + ], + ), + migrations.CreateModel( + name='MysteryBox', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('description', models.TextField()), + ('price', models.DecimalField(decimal_places=2, max_digits=10)), + ('category', models.CharField(default='mixed', max_length=100)), + ('stock_quantity', models.PositiveIntegerField(default=0)), + ('is_featured', models.BooleanField(default=False)), + ('products', models.ManyToManyField(blank=True, to='Boxes.product')), + ], + ), + ] diff --git a/Boxes/migrations/0002_mysterybox_image.py b/Boxes/migrations/0002_mysterybox_image.py new file mode 100644 index 0000000000000000000000000000000000000000..a5ca3b3e69e172e92fac6180ffe28ce9ecbfb2c7 --- /dev/null +++ b/Boxes/migrations/0002_mysterybox_image.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-08-20 04:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('Boxes', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='mysterybox', + name='image', + field=models.ImageField(blank=True, null=True, upload_to=''), + ), + ] diff --git a/Boxes/migrations/0003_alter_product_category.py b/Boxes/migrations/0003_alter_product_category.py new file mode 100644 index 0000000000000000000000000000000000000000..46fc95fd166fc6b108029cd2577dc3c31cab9cdd --- /dev/null +++ b/Boxes/migrations/0003_alter_product_category.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-08-23 06:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('Boxes', '0002_mysterybox_image'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='category', + field=models.CharField(choices=[('Electronics', 'Electronics'), ('Home & Kitchen', 'In Home & Kitchen'), ('Beauty & Personal Care', 'Beauty & Personal Care'), ('Sports & Fitness', 'Sports & Fitness'), ('Books & stationery', 'Books & stationery')], default='Electronics', max_length=100), + ), + ] diff --git a/Boxes/migrations/__init__.py b/Boxes/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Boxes/migrations/__pycache__/0001_initial.cpython-313.pyc b/Boxes/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a37dddbdcee83adc3ca4c75e7ef5bb90d7cbf9e Binary files /dev/null and b/Boxes/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/Boxes/migrations/__pycache__/0002_mysterybox_image.cpython-313.pyc b/Boxes/migrations/__pycache__/0002_mysterybox_image.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32668f06d30ec137d64d29b5fb3e4b87c96c809b Binary files /dev/null and b/Boxes/migrations/__pycache__/0002_mysterybox_image.cpython-313.pyc differ diff --git a/Boxes/migrations/__pycache__/0003_alter_product_category.cpython-313.pyc b/Boxes/migrations/__pycache__/0003_alter_product_category.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c780da57ac83b510b66f43f3296647cf7d21231 Binary files /dev/null and b/Boxes/migrations/__pycache__/0003_alter_product_category.cpython-313.pyc differ diff --git a/Boxes/migrations/__pycache__/__init__.cpython-313.pyc b/Boxes/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01a2ceae6c618d5c8b328d25c5dc6955787e7034 Binary files /dev/null and b/Boxes/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/Boxes/models.py b/Boxes/models.py new file mode 100644 index 0000000000000000000000000000000000000000..e1b39327ace1dc82557f7263bef2554246f7f757 --- /dev/null +++ b/Boxes/models.py @@ -0,0 +1,54 @@ +from django.db import models + +class Product(models.Model): + CONDITION_CHOICES = [ + ('new', 'New'), + ('returned', 'Returned'), + ('like_new', 'Like New'), + ] + STATUS_CHOICES = [ + ('available', 'Available'), + ('in_box', 'In Box'), + ('sold', 'Sold'), + ] + CATEGORY = [ + ('Electronics', 'Electronics'), + ('Home & Kitchen', 'In Home & Kitchen'), + ('Beauty & Personal Care', 'Beauty & Personal Care'), + ('Sports & Fitness', 'Sports & Fitness'), + ('Books & stationery', 'Books & stationery'), + + ] + + name = models.CharField(max_length=200) + brand = models.CharField(max_length=100, blank=True, null=True) + category = models.CharField(max_length=100 , choices=CATEGORY , default='Electronics') + original_price = models.DecimalField(max_digits=10, decimal_places=2) + condition = models.CharField(max_length=20, choices=CONDITION_CHOICES, default='like_new') + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='available') + seller_name = models.CharField(max_length=150, blank=True, null=True) + + def __str__(self): + return f"{self.name} ({self.brand})" + + +class MysteryBox(models.Model): + title = models.CharField(max_length=200) + description = models.TextField() + image = models.ImageField(null=True , blank= True) + price = models.DecimalField(max_digits=10, decimal_places=2) + category = models.CharField(max_length=100, default='mixed') + products = models.ManyToManyField(Product, blank=True) + stock_quantity = models.PositiveIntegerField(default=0) + is_featured = models.BooleanField(default=False) + + def __str__(self): + return self.title + + @property + def imageURL(self): + try: + url=self.image.url + except: + url='' + return url diff --git a/Boxes/templates/all_boxes.html b/Boxes/templates/all_boxes.html new file mode 100644 index 0000000000000000000000000000000000000000..7b1110128d19db7d019b5285cc9f3716241988d3 --- /dev/null +++ b/Boxes/templates/all_boxes.html @@ -0,0 +1,141 @@ + + + + + + All Mystery Boxes | MysteryBox + + + + + +
+
+ + + + 🎁 MysteryBox + + + + + + + + +
+
+ + +
+ +
+ + {% for box in boxes %} + + + + {{ box.title }} + +
+

{{ box.title }}

+

β‚Ή{{ box.price }}

+ + View Details + +
+
+ {% empty %} +

+ No Mystery Boxes found 😒 +

+ {% endfor %} + +
+
+ + + + + + + diff --git a/Boxes/templates/box_detail.html b/Boxes/templates/box_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..47d87d70436586756ec2cbac144fcc735e5fd385 --- /dev/null +++ b/Boxes/templates/box_detail.html @@ -0,0 +1,162 @@ + + + + + + Mystery Box Details | MysteryBox + + + + + +
+
+ + + + 🎁 MysteryBox + + + + + + + + +
+
+ + +
+ +
+ + +
+ {{ box.title }} +
+ + +
+

{{ box.title }}

+ +

{{ box.description }}

+ +

Price: β‚Ή{{ box.price }}

+ + {% if box.stock_quantity > 0 %} +

In Stock: {{ box.stock_quantity }}

+ {% else %} +

Out of Stock

+ {% endif %} + + +
+ + Add to Cart πŸ›’ + + +
+ + + {% if box.products.exists %} +
+

Items inside (possible contents):

+
    + {% for product in box.products.all %} +
  • {{ product.name }}
  • + {% endfor %} +
+
+ {% endif %} +
+
+
+ + + + + + + diff --git a/Boxes/templates/spin_wheel.html b/Boxes/templates/spin_wheel.html new file mode 100644 index 0000000000000000000000000000000000000000..1896193fdb5236443c1ae67eb66f6d61fd8efc23 --- /dev/null +++ b/Boxes/templates/spin_wheel.html @@ -0,0 +1,374 @@ + + + + + + Spin the Wheel - MysteryMart + + + + + + + +
+
+
+ +
+

MysteryMart

+
+ +
+ + +
+
+ + +
+ +
+

+ 🎑 Spin & Win Rewards! +

+

+ Test your luck! Spin the wheel and grab amazing rewards. + Every user gets 1 free spin per day! +

+ +
+
+ +
+

Your spins: 1

+
+
+ +
+ +
+
+
+
+
+ +
+ +
+ β‚Ή100 OFF +
+
+ Free Box +
+
+ 5% OFF +
+
+ Try Again +
+
+ 10% OFF +
+
+ β‚Ή200 OFF +
+
+ + +
+
+
+ + +
+

Your Rewards

+ +
+
+
+
+ +
+
+

Discount Coupons

+

3 active

+
+
+ +
+ +
+
+
+ +
+
+

Free Mystery Boxes

+

1 available

+
+
+ +
+
+ +
+

Today's Special

+

Spin now for double rewards chance!

+
+
+
+ + + + + + +
+ + +
+

✨ How it works

+
+
+
+ +
+

Login Daily

+

Log in to your MysteryMart account to get your free daily spin

+
+ +
+
+ +
+

Instant Rewards

+

Your reward will be automatically added to your wallet or coupons

+
+ +
+
+ +
+

Share & Earn

+

Share your win with friends to earn extra spins

+
+ +
+
+ +
+

Daily Bonuses

+

Come back every day for bigger rewards and special bonuses

+
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/Boxes/tests.py b/Boxes/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..de8bdc00eb2fed53494a534d48e400faa830dbd9 --- /dev/null +++ b/Boxes/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Boxes/urls.py b/Boxes/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..1edcb6d2c125fd9a3e65afb835e81a47c060224f --- /dev/null +++ b/Boxes/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from . import views + +app_name = "boxes" + +urlpatterns = [ + path("", views.all_boxes, name="all_boxes"), + path("/", views.box_detail, name="box_detail"), + +] \ No newline at end of file diff --git a/Boxes/views.py b/Boxes/views.py new file mode 100644 index 0000000000000000000000000000000000000000..5fdf4e9a4cf42acd2e3f2786f1b3850f240c521d --- /dev/null +++ b/Boxes/views.py @@ -0,0 +1,14 @@ +from django.shortcuts import render, get_object_or_404 +from .models import MysteryBox + + +def all_boxes(request): + boxes = MysteryBox.objects.all() + return render(request, "Boxes/templates/all_boxes.html", {"boxes": boxes}) + +def box_detail(request, pk): + box = get_object_or_404(MysteryBox, pk=pk) + return render(request, "Boxes/templates/box_detail.html", {"box": box}) + + + diff --git a/Gamification/__init__.py b/Gamification/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Gamification/__pycache__/__init__.cpython-313.pyc b/Gamification/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d2fc6873d52d86a3262c405431bbe380bf32e92 Binary files /dev/null and b/Gamification/__pycache__/__init__.cpython-313.pyc differ diff --git a/Gamification/__pycache__/admin.cpython-313.pyc b/Gamification/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b18ce62d0902eb06437fb3543fcba2716496f9a Binary files /dev/null and b/Gamification/__pycache__/admin.cpython-313.pyc differ diff --git a/Gamification/__pycache__/apps.cpython-313.pyc b/Gamification/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36d0f4848eca866153046cef2c79dc935fb0514a Binary files /dev/null and b/Gamification/__pycache__/apps.cpython-313.pyc differ diff --git a/Gamification/__pycache__/models.cpython-313.pyc b/Gamification/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a0a95e33c100ae6f24e32c3252cb8cd09f19e47 Binary files /dev/null and b/Gamification/__pycache__/models.cpython-313.pyc differ diff --git a/Gamification/admin.py b/Gamification/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..7f2585254a3d25d69f219804e0891793380b77b5 --- /dev/null +++ b/Gamification/admin.py @@ -0,0 +1,19 @@ +from django.contrib import admin +from .models import Reward, SpinWheelHistory + + +class RewardAdmin(admin.ModelAdmin): + list_display = ('user', 'reward_type', 'value', 'redeemed', 'created_at') + list_filter = ('reward_type', 'redeemed') + search_fields = ('user__email',) + + +class SpinWheelHistoryAdmin(admin.ModelAdmin): + list_display = ('user', 'reward', 'spun_at') + list_filter = ('spun_at',) + search_fields = ('user__email',) + + +admin.site.register(Reward, RewardAdmin) +admin.site.register(SpinWheelHistory, SpinWheelHistoryAdmin) + diff --git a/Gamification/apps.py b/Gamification/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..244c6854e5d53de590af366619ff77a73a6ec9be --- /dev/null +++ b/Gamification/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GamificationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Gamification' diff --git a/Gamification/migrations/0001_initial.py b/Gamification/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..4082d3c00aa672a60c77e50c73c6eb39fece07fb --- /dev/null +++ b/Gamification/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.4 on 2025-08-18 10:42 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('Accounts', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Reward', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reward_type', models.CharField(choices=[('discount', 'Discount'), ('free_box', 'Free Box'), ('coupon', 'Coupon'), ('credits', 'Credits')], max_length=20)), + ('value', models.CharField(max_length=100)), + ('redeemed', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Accounts.profile')), + ], + ), + migrations.CreateModel( + name='SpinWheelHistory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('spun_at', models.DateTimeField(auto_now_add=True)), + ('reward', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='Gamification.reward')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Accounts.profile')), + ], + ), + ] diff --git a/Gamification/migrations/__init__.py b/Gamification/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Gamification/migrations/__pycache__/0001_initial.cpython-313.pyc b/Gamification/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8bbc850c5824a932b644c50cf7b02f089c3634c Binary files /dev/null and b/Gamification/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/Gamification/migrations/__pycache__/__init__.cpython-313.pyc b/Gamification/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..634c84e8711a02fa12c38871571496e6d706688b Binary files /dev/null and b/Gamification/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/Gamification/models.py b/Gamification/models.py new file mode 100644 index 0000000000000000000000000000000000000000..d4740e93e08cde422c343d8f75aa3d703cd7ed9e --- /dev/null +++ b/Gamification/models.py @@ -0,0 +1,28 @@ +from django.db import models +from Accounts.models import Profile + +class Reward(models.Model): + REWARD_TYPES = [ + ('discount', 'Discount'), + ('free_box', 'Free Box'), + ('coupon', 'Coupon'), + ('credits', 'Credits'), + ] + + user = models.ForeignKey(Profile, on_delete=models.CASCADE) + reward_type = models.CharField(max_length=20, choices=REWARD_TYPES) + value = models.CharField(max_length=100) + redeemed = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.reward_type} ({self.value}) for {self.user.user.username}" + + +class SpinWheelHistory(models.Model): + user = models.ForeignKey(Profile, on_delete=models.CASCADE) + reward = models.ForeignKey(Reward, on_delete=models.SET_NULL, null=True, blank=True) + spun_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.user.user.username} spun at {self.spun_at}" diff --git a/Gamification/tests.py b/Gamification/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..de8bdc00eb2fed53494a534d48e400faa830dbd9 --- /dev/null +++ b/Gamification/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Gamification/urls.py b/Gamification/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Gamification/views.py b/Gamification/views.py new file mode 100644 index 0000000000000000000000000000000000000000..c60c790437030748012ed3b77ed8708956de9957 --- /dev/null +++ b/Gamification/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/MistryMart/__init__.py b/MistryMart/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/MistryMart/__pycache__/__init__.cpython-313.pyc b/MistryMart/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c727b9506254120262175dc83085b956ad89eedf Binary files /dev/null and b/MistryMart/__pycache__/__init__.cpython-313.pyc differ diff --git a/MistryMart/__pycache__/settings.cpython-313.pyc b/MistryMart/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6eec702f0a75c729ae165bc40434910f4ff8089c Binary files /dev/null and b/MistryMart/__pycache__/settings.cpython-313.pyc differ diff --git a/MistryMart/__pycache__/urls.cpython-313.pyc b/MistryMart/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1f886151abffdea8d60fa945da7eff2c87d2b0f Binary files /dev/null and b/MistryMart/__pycache__/urls.cpython-313.pyc differ diff --git a/MistryMart/__pycache__/wsgi.cpython-313.pyc b/MistryMart/__pycache__/wsgi.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15401a3b1983ad389fab574c17951b5e890bf7cd Binary files /dev/null and b/MistryMart/__pycache__/wsgi.cpython-313.pyc differ diff --git a/MistryMart/asgi.py b/MistryMart/asgi.py new file mode 100644 index 0000000000000000000000000000000000000000..15513c4456d4216352b2d01f6580507bdf54de59 --- /dev/null +++ b/MistryMart/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for MistryMart project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MistryMart.settings') + +application = get_asgi_application() diff --git a/MistryMart/settings.py b/MistryMart/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..bf7bf9870038d3c72dbf8b4edeb3b5883761cfb2 --- /dev/null +++ b/MistryMart/settings.py @@ -0,0 +1,152 @@ +""" +Django settings for MistryMart project. + +Generated by 'django-admin startproject' using Django 5.2.4. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.2/ref/settings/ +""" + +from pathlib import Path +import os + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-ku3+qwo=e#h7@ur7v=tmrr_=6ihz_y)w6p9$b1ix&oy^+lh_7b' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'Accounts', + 'Analytics', + 'Boxes', + 'Gamification', + 'Home', + 'cart', + +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +AUTH_USER_MODEL = 'Accounts.User' + + +ROOT_URLCONF = 'MistryMart.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR , 'templates'], + 'DIRS': [BASE_DIR , 'Accounts/templates'], + 'DIRS': [BASE_DIR , 'Analytics/templates'], + 'DIRS': [BASE_DIR , 'Boxes/templates'], + 'DIRS': [BASE_DIR , 'Gamification/templates'], + 'DIRS': [BASE_DIR , 'Home/templates'], + 'DIRS': [BASE_DIR , 'Order/templates'], + + + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'MistryMart.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.2/howto/static-files/ + + +STATIC_URL = 'static/' +STATICFILES_DIRS = [ + os.path.join(BASE_DIR,'static'), + + # os.path.join(BASE_DIR,'Home/static') +] +# STATIC_ROOT = os.path.join(BASE_DIR, 'Home/static') + + +MEDIA_URL='/images/' +MEDIA_ROOT = os.path.join(BASE_DIR , 'static/images') + +# Default primary key field type +# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/MistryMart/urls.py b/MistryMart/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..ba4b867ec599a890ecb3ce602ed5065a1e4d0c3d --- /dev/null +++ b/MistryMart/urls.py @@ -0,0 +1,29 @@ +""" +URL configuration for MistryMart project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path , include +from django.conf.urls.static import static +from django.conf import settings + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include("Home.urls")), + path('Accounts/', include("Accounts.urls")), + path('boxes/', include("Boxes.urls")), + path("cart/", include("cart.urls")), +] +urlpatterns += static(settings.MEDIA_URL , document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/MistryMart/wsgi.py b/MistryMart/wsgi.py new file mode 100644 index 0000000000000000000000000000000000000000..bc969735eeb8a968bb8b484e76dbe08d0098203b --- /dev/null +++ b/MistryMart/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for MistryMart project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MistryMart.settings') + +application = get_wsgi_application() diff --git a/cart/__init__.py b/cart/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/cart/__pycache__/__init__.cpython-313.pyc b/cart/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4211e1f9a398ae303f95cf3640d1ccaba3e6000 Binary files /dev/null and b/cart/__pycache__/__init__.cpython-313.pyc differ diff --git a/cart/__pycache__/admin.cpython-313.pyc b/cart/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..260fbea81ba460f30141ec1a473bbe475bdc149e Binary files /dev/null and b/cart/__pycache__/admin.cpython-313.pyc differ diff --git a/cart/__pycache__/apps.cpython-313.pyc b/cart/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2a0f5cb9d813029ad2b848053987b41ba6672d1 Binary files /dev/null and b/cart/__pycache__/apps.cpython-313.pyc differ diff --git a/cart/__pycache__/models.cpython-313.pyc b/cart/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19da347f8553892f9fa4b315efa7f0c3f0ea10d2 Binary files /dev/null and b/cart/__pycache__/models.cpython-313.pyc differ diff --git a/cart/__pycache__/urls.cpython-313.pyc b/cart/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ef759ec991b794f9961cb45910731c3cc72b238 Binary files /dev/null and b/cart/__pycache__/urls.cpython-313.pyc differ diff --git a/cart/__pycache__/views.cpython-313.pyc b/cart/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d6bdd5ca4ff0096351d5f15a7f09df91c91144f Binary files /dev/null and b/cart/__pycache__/views.cpython-313.pyc differ diff --git a/cart/admin.py b/cart/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..46b4f2a63d2635b6a871b8c21f23e742fe8be50c --- /dev/null +++ b/cart/admin.py @@ -0,0 +1,46 @@ +from django.contrib import admin +from .models import Cart, CartItem, Order, OrderItem, Payment + + + +class CartItemInline(admin.TabularInline): + model = CartItem + extra = 0 + readonly_fields = ("box", "quantity", "subtotal") + + + + +@admin.register(Cart) +class CartAdmin(admin.ModelAdmin): + list_display = ("id", "user", "created_at", "total_amount_display") + inlines = [CartItemInline] + + def total_amount_display(self, obj): + return obj.total_amount + total_amount_display.short_description = "Total Amount" + + + +class OrderItemInline(admin.TabularInline): + model = OrderItem + extra = 0 + readonly_fields = ("box", "quantity", "unit_price", "subtotal") + + + + +@admin.register(Order) +class OrderAdmin(admin.ModelAdmin): + list_display = ("order_number", "user", "total_amount", "status", "created_at") + list_filter = ("status", "created_at") + search_fields = ("user__user__email",) + inlines = [OrderItemInline] + readonly_fields = ("order_number", "created_at") + + +@admin.register(Payment) +class PaymentAdmin(admin.ModelAdmin): + list_display = ("order", "payment_method", "status", "transaction_id", "paid_at") + list_filter = ("payment_method", "status") + search_fields = ("order__order_number", "transaction_id") diff --git a/cart/apps.py b/cart/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..37acb3506628d768fac86cbf974880636facb446 --- /dev/null +++ b/cart/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CartConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'cart' diff --git a/cart/migrations/0001_initial.py b/cart/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..8c90fb9c2960ab34b93983a5ff8a619f5b801e6f --- /dev/null +++ b/cart/migrations/0001_initial.py @@ -0,0 +1,66 @@ +# Generated by Django 5.2.4 on 2025-08-21 12:31 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('Accounts', '0001_initial'), + ('Boxes', '0002_mysterybox_image'), + ] + + operations = [ + migrations.CreateModel( + name='Cart', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='cart', to='Accounts.profile')), + ], + ), + migrations.CreateModel( + name='CartItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=1)), + ('box', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Boxes.mysterybox')), + ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='cart.cart')), + ], + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_number', models.CharField(editable=False, max_length=50, unique=True)), + ('total_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('paid', 'Paid'), ('shipped', 'Shipped'), ('delivered', 'Delivered'), ('cancelled', 'Cancelled')], default='pending', max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='Accounts.profile')), + ], + ), + migrations.CreateModel( + name='OrderItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=1)), + ('unit_price', models.DecimalField(decimal_places=2, max_digits=10)), + ('box', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Boxes.mysterybox')), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='cart.order')), + ], + ), + migrations.CreateModel( + name='Payment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('payment_method', models.CharField(choices=[('stripe', 'Stripe'), ('paypal', 'PayPal'), ('razorpay', 'Razorpay'), ('cod', 'Cash on Delivery')], default='cod', max_length=20)), + ('transaction_id', models.CharField(blank=True, max_length=150, null=True)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('successful', 'Successful'), ('failed', 'Failed')], default='pending', max_length=20)), + ('paid_at', models.DateTimeField(blank=True, null=True)), + ('order', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payment', to='cart.order')), + ], + ), + ] diff --git a/cart/migrations/0002_alter_orderitem_unit_price.py b/cart/migrations/0002_alter_orderitem_unit_price.py new file mode 100644 index 0000000000000000000000000000000000000000..d8773d8ec11d9b930ced30fbed3801c428e64229 --- /dev/null +++ b/cart/migrations/0002_alter_orderitem_unit_price.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-08-28 13:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cart', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='orderitem', + name='unit_price', + field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10), + ), + ] diff --git a/cart/migrations/__init__.py b/cart/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/cart/migrations/__pycache__/0001_initial.cpython-313.pyc b/cart/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22b9591de1036babb6fbd6f439303d052cc87718 Binary files /dev/null and b/cart/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/cart/migrations/__pycache__/0002_alter_orderitem_unit_price.cpython-313.pyc b/cart/migrations/__pycache__/0002_alter_orderitem_unit_price.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54ea10ccbf3bbfa13282fe697af5ce28edce3882 Binary files /dev/null and b/cart/migrations/__pycache__/0002_alter_orderitem_unit_price.cpython-313.pyc differ diff --git a/cart/migrations/__pycache__/__init__.cpython-313.pyc b/cart/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75106a73ea8b19491279aad0174279c826ac4fa2 Binary files /dev/null and b/cart/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/cart/models.py b/cart/models.py new file mode 100644 index 0000000000000000000000000000000000000000..0549e05154fa304ca6940e7b2f22b338cd293177 --- /dev/null +++ b/cart/models.py @@ -0,0 +1,102 @@ +from django.db import models +from django.db.models.signals import post_save, post_delete +from django.dispatch import receiver +from Accounts.models import Profile +from Boxes.models import MysteryBox +from django.utils.crypto import get_random_string +from django.utils.timezone import now + + +class Cart(models.Model): + user = models.OneToOneField(Profile, on_delete=models.CASCADE, related_name="cart") + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Cart of {self.user.user.email}" + + @property + def total_amount(self): + return sum(item.subtotal for item in self.items.all()) + + +class CartItem(models.Model): + cart = models.ForeignKey(Cart, on_delete=models.CASCADE, related_name="items") + box = models.ForeignKey(MysteryBox, on_delete=models.CASCADE) + quantity = models.PositiveIntegerField(default=1) + + def __str__(self): + return f"{self.quantity} Γ— {self.box.title}" + + @property + def subtotal(self): + return (self.box.price or 0) * (self.quantity or 0) + + +class Order(models.Model): + STATUS_CHOICES = [ + ("pending", "Pending"), + ("paid", "Paid"), + ("shipped", "Shipped"), + ("delivered", "Delivered"), + ("cancelled", "Cancelled"), + ] + + user = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="orders") + order_number = models.CharField(max_length=50, unique=True, editable=False) + total_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="pending") + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Order {self.order_number} ({self.user.user.email})" + + def save(self, *args, **kwargs): + # Auto-generate order number if missing + if not self.order_number: + self.order_number = f"ORD{now().strftime('%Y%m%d')}{get_random_string(5).upper()}" + super().save(*args, **kwargs) + + +class OrderItem(models.Model): + order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="items") + box = models.ForeignKey(MysteryBox, on_delete=models.CASCADE) + quantity = models.PositiveIntegerField(default=1) + unit_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) + + def __str__(self): + return f"{self.quantity} Γ— {self.box.title}" + + @property + def subtotal(self): + return (self.unit_price or 0) * (self.quantity or 0) + + +class Payment(models.Model): + METHOD_CHOICES = [ + ("stripe", "Stripe"), + ("paypal", "PayPal"), + ("razorpay", "Razorpay"), + ("cod", "Cash on Delivery"), + ] + STATUS_CHOICES = [ + ("pending", "Pending"), + ("successful", "Successful"), + ("failed", "Failed"), + ] + + order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name="payment") + payment_method = models.CharField(max_length=20, choices=METHOD_CHOICES, default="cod") + transaction_id = models.CharField(max_length=150, blank=True, null=True) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="pending") + paid_at = models.DateTimeField(blank=True, null=True) + + def __str__(self): + return f"Payment {self.status} - {self.order.order_number}" + + +# πŸ”Ή Signals to auto-update Order total when OrderItems change +@receiver([post_save, post_delete], sender=OrderItem) +def update_order_total(sender, instance, **kwargs): + order = instance.order + order.total_amount = sum(item.subtotal for item in order.items.all()) + order.save(update_fields=["total_amount"]) diff --git a/cart/templates/cart.html b/cart/templates/cart.html new file mode 100644 index 0000000000000000000000000000000000000000..edd395e5c52eaf90d88b2653f828eba3eba9254d --- /dev/null +++ b/cart/templates/cart.html @@ -0,0 +1,124 @@ +{% load static %} + + + + + My Cart + + + + + +
+ +
+ +
+ +

πŸ›’ Shopping Cart

+ + {% if cart.items.all %} + +
+ + + + + + + + + + + + {% for item in cart.items.all %} + + + + + + + + {% endfor %} + +
BoxPriceQtySubtotal
{{ item.box.title }}β‚Ή{{ item.box.price }}{{ item.quantity }}β‚Ή{{ item.subtotal }} + Remove +
+
+ + +
+

Total: β‚Ή{{ cart.total_amount }}

+ + + Proceed to Checkout βœ… + +
+ + {% else %} +

Your cart is empty 🎁

+ {% endif %} + + + +
+ + + diff --git a/cart/templates/checkout_success.html b/cart/templates/checkout_success.html new file mode 100644 index 0000000000000000000000000000000000000000..bdf4a8b851a16827233d3199da70e1b700ef2b6d --- /dev/null +++ b/cart/templates/checkout_success.html @@ -0,0 +1,23 @@ + + + + + Order Success + + + + +
+

πŸŽ‰ Order Placed Successfully!

+

Your order #{{ order.order_number }} has been created.

+

We’ll notify you as soon as your items are shipped 🚚

+ + + + Back to Shop + +
+ + + diff --git a/cart/tests.py b/cart/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..de8bdc00eb2fed53494a534d48e400faa830dbd9 --- /dev/null +++ b/cart/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/cart/urls.py b/cart/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..13f09fe747db42075fec770ed97f2e504190c91e --- /dev/null +++ b/cart/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from . import views + +app_name = "cart" + +urlpatterns = [ + path("", views.cart_detail, name="cart_detail"), + path("add//", views.cart_add, name="cart_add"), + path("remove//", views.cart_remove, name="cart_remove"), + path("checkout/", views.checkout, name="checkout"), +] diff --git a/cart/views.py b/cart/views.py new file mode 100644 index 0000000000000000000000000000000000000000..2e5aa2b80f8eba31648cc80d00aee14612469fae --- /dev/null +++ b/cart/views.py @@ -0,0 +1,69 @@ +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth.decorators import login_required +from django.utils.timezone import now +from Boxes.models import MysteryBox +from Accounts.models import Profile +from .models import Cart, CartItem, Order, OrderItem, Payment + + + +@login_required +def cart_detail(request): + """Show all items in the cart""" + profile = request.user.profile + cart, created = Cart.objects.get_or_create(user=profile) + + return render(request, "cart/templates/cart.html", {"cart": cart}) + + +@login_required +def cart_add(request, pk): + """Add an item (MysteryBox) to the cart""" + profile = request.user.profile + cart, created = Cart.objects.get_or_create(user=profile) + box = get_object_or_404(MysteryBox, pk=pk) + + item, created = CartItem.objects.get_or_create(cart=cart, box=box) + item.quantity += 1 if not created else 1 + item.save() + + return redirect("cart:cart_detail") + + +@login_required +def cart_remove(request, pk): + """Remove one item from the cart""" + profile = request.user.profile + cart = get_object_or_404(Cart, user=profile) + item = get_object_or_404(CartItem, cart=cart, pk=pk) + item.delete() + return redirect("cart:cart_detail") + + + +@login_required +def checkout(request): + """Convert cart into an order""" + profile = request.user.profile + cart = get_object_or_404(Cart, user=profile) + + if not cart.items.exists(): + return redirect("cart:cart_detail") + + + order = Order.objects.create( + user=profile, + total_amount=cart.total_amount + ) + + for item in cart.items.all(): + OrderItem.objects.create( + order=order, + box=item.box, + quantity=item.quantity, + unit_price=item.box.price + ) + + cart.items.all().delete() + + return render(request, "cart/templates/checkout_success.html", {"order": order}) diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..1111453f3d5393a1bab33d9602a01789ea0cf805 --- /dev/null +++ b/db.sqlite3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:652737c60c7ddb7e5f8eb2197943ef7376254ff9ae204e1c92e1589f5b429b9a +size 323584 diff --git a/manage.py b/manage.py new file mode 100644 index 0000000000000000000000000000000000000000..226779e8e1ce77746809eab12c2453c3fa4e040d --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MistryMart.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/static/images/WhatsApp_Image_2025-08-16_at_10.06.25_PM.jpeg b/static/images/WhatsApp_Image_2025-08-16_at_10.06.25_PM.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..fe0b014167a028c5965c7e42026ac7e397f48cb7 --- /dev/null +++ b/static/images/WhatsApp_Image_2025-08-16_at_10.06.25_PM.jpeg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a1a1ee02c32d242d07676c61bf01f38dec22838a0da56a45a1832c3034cdea1 +size 144545 diff --git a/static/images/wrapped-gift_1f381.png b/static/images/wrapped-gift_1f381.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbef8b56d057e6ce5eb4d2e43d27e8e19caf15a Binary files /dev/null and b/static/images/wrapped-gift_1f381.png differ diff --git a/static/images/wrapped-gift_1f381_1r4IGJz.png b/static/images/wrapped-gift_1f381_1r4IGJz.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbef8b56d057e6ce5eb4d2e43d27e8e19caf15a Binary files /dev/null and b/static/images/wrapped-gift_1f381_1r4IGJz.png differ diff --git a/static/images/wrapped-gift_1f381_4bD6LaP.png b/static/images/wrapped-gift_1f381_4bD6LaP.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbef8b56d057e6ce5eb4d2e43d27e8e19caf15a Binary files /dev/null and b/static/images/wrapped-gift_1f381_4bD6LaP.png differ diff --git a/static/images/wrapped-gift_1f381_O5bHTW6.png b/static/images/wrapped-gift_1f381_O5bHTW6.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbef8b56d057e6ce5eb4d2e43d27e8e19caf15a Binary files /dev/null and b/static/images/wrapped-gift_1f381_O5bHTW6.png differ diff --git a/static/images/wrapped-gift_1f381_XhbfpW2.png b/static/images/wrapped-gift_1f381_XhbfpW2.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbef8b56d057e6ce5eb4d2e43d27e8e19caf15a Binary files /dev/null and b/static/images/wrapped-gift_1f381_XhbfpW2.png differ diff --git a/static/images/wrapped-gift_1f381_lDnnoen.png b/static/images/wrapped-gift_1f381_lDnnoen.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbef8b56d057e6ce5eb4d2e43d27e8e19caf15a Binary files /dev/null and b/static/images/wrapped-gift_1f381_lDnnoen.png differ diff --git a/static/images/wrapped-gift_1f381_wbAvPJM.png b/static/images/wrapped-gift_1f381_wbAvPJM.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbef8b56d057e6ce5eb4d2e43d27e8e19caf15a Binary files /dev/null and b/static/images/wrapped-gift_1f381_wbAvPJM.png differ diff --git a/static/images/wrapped-gift_1f381_ww0BeTb.png b/static/images/wrapped-gift_1f381_ww0BeTb.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbef8b56d057e6ce5eb4d2e43d27e8e19caf15a Binary files /dev/null and b/static/images/wrapped-gift_1f381_ww0BeTb.png differ