diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..7df03ee 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..781ff54 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/pexels.cpython-311.pyc b/core/__pycache__/pexels.cpython-311.pyc new file mode 100644 index 0000000..dfd60f5 Binary files /dev/null and b/core/__pycache__/pexels.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..ba4a408 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 2a36fd6..9380724 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..f779753 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,27 @@ from django.contrib import admin +from .models import Category, Product, Order, OrderItem, Profile -# Register your models here. +@admin.register(Category) +class CategoryAdmin(admin.ModelAdmin): + list_display = ('name', 'description') + +@admin.register(Product) +class ProductAdmin(admin.ModelAdmin): + list_display = ('title', 'category', 'price', 'stock', 'created_at') + list_filter = ('category', 'created_at') + search_fields = ('title', 'description') + +class OrderItemInline(admin.TabularInline): + model = OrderItem + extra = 1 + +@admin.register(Order) +class OrderAdmin(admin.ModelAdmin): + list_display = ('id', 'user', 'order_date', 'total_amount', 'status') + list_filter = ('status', 'order_date') + inlines = [OrderItemInline] + +@admin.register(Profile) +class ProfileAdmin(admin.ModelAdmin): + list_display = ('user', 'role') + list_filter = ('role',) \ 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..c311931 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..72cea61 Binary files /dev/null and b/core/management/commands/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__pycache__/seed_data.cpython-311.pyc b/core/management/commands/__pycache__/seed_data.cpython-311.pyc new file mode 100644 index 0000000..929ef82 Binary files /dev/null and b/core/management/commands/__pycache__/seed_data.cpython-311.pyc differ diff --git a/core/management/commands/seed_data.py b/core/management/commands/seed_data.py new file mode 100644 index 0000000..f2c92a7 --- /dev/null +++ b/core/management/commands/seed_data.py @@ -0,0 +1,50 @@ +from django.core.management.base import BaseCommand +from core.models import Category, Product +from core.pexels import fetch_first +import random + +class Command(BaseCommand): + help = 'Seeds the database with initial categories and products' + + def handle(self, *args, **kwargs): + categories_data = [ + {'name': 'Tech', 'description': 'Latest gadgets and hardware.'}, + {'name': 'Lifestyle', 'description': 'Modern living essentials.'}, + {'name': 'Minimalist', 'description': 'Simple and clean aesthetics.'}, + ] + + for cat_data in categories_data: + category, created = Category.objects.get_or_create( + name=cat_data['name'], + defaults={'description': cat_data['description']} + ) + if created: + self.stdout.write(f"Created category: {category.name}") + + # Sample products + products_data = [ + ('Minimal Desk Lamp', 'Tech', 89.99, 'minimalist desk lamp'), + ('Mechanical Keyboard', 'Tech', 159.00, 'mechanical keyboard'), + ('Premium Notebook', 'Minimalist', 25.50, 'aesthetic notebook'), + ('Leather Wallet', 'Lifestyle', 45.00, 'leather wallet'), + ('Wireless Earbuds', 'Tech', 129.00, 'modern wireless earbuds'), + ('Ceramic Coffee Mug', 'Minimalist', 18.00, 'ceramic mug minimalist'), + ] + + for title, cat_name, price, query in products_data: + category = Category.objects.get(name=cat_name) + if not Product.objects.filter(title=title).exists(): + img_data = fetch_first(query) + img_url = "" + if img_data: + img_url = img_data['local_path'] + + Product.objects.create( + title=title, + category=category, + price=price, + description=f"This is a premium {title.lower()} designed for quality and style.", + stock=random.randint(10, 50), + image_url=img_url + ) + self.stdout.write(f"Created product: {title}") diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..6d95f82 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,70 @@ +# Generated by Django 5.2.7 on 2026-02-07 10:01 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('description', models.TextField(blank=True)), + ], + options={ + 'verbose_name_plural': 'Categories', + }, + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_date', models.DateTimeField(auto_now_add=True)), + ('total_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('paid', 'Paid'), ('shipped', 'Shipped'), ('cancelled', 'Cancelled')], default='pending', max_length=10)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Product', + 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)), + ('stock', models.PositiveIntegerField(default=0)), + ('image_url', models.URLField(blank=True, max_length=500)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='core.category')), + ], + ), + migrations.CreateModel( + name='OrderItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=1)), + ('price', models.DecimalField(decimal_places=2, max_digits=10)), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.order')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product')), + ], + ), + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('role', models.CharField(choices=[('admin', 'Admin'), ('customer', 'Customer')], default='customer', max_length=10)), + ('avatar', models.ImageField(blank=True, null=True, upload_to='avatars/')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..626015f Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..b3d488d 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,71 @@ from django.db import models +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver -# Create your models here. +class Profile(models.Model): + ROLE_CHOICES = ( + ('admin', 'Admin'), + ('customer', 'Customer'), + ) + user = models.OneToOneField(User, on_delete=models.CASCADE) + role = models.CharField(max_length=10, choices=ROLE_CHOICES, default='customer') + avatar = models.ImageField(upload_to='avatars/', null=True, blank=True) + + def __str__(self): + return f"{self.user.username} - {self.role}" + +class Category(models.Model): + name = models.CharField(max_length=100) + description = models.TextField(blank=True) + + class Meta: + verbose_name_plural = "Categories" + + def __str__(self): + return self.name + +class Product(models.Model): + category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE) + title = models.CharField(max_length=200) + description = models.TextField() + price = models.DecimalField(max_digits=10, decimal_places=2) + stock = models.PositiveIntegerField(default=0) + image_url = models.URLField(max_length=500, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.title + +class Order(models.Model): + STATUS_CHOICES = ( + ('pending', 'Pending'), + ('paid', 'Paid'), + ('shipped', 'Shipped'), + ('cancelled', 'Cancelled'), + ) + user = models.ForeignKey(User, related_name='orders', on_delete=models.CASCADE) + order_date = models.DateTimeField(auto_now_add=True) + total_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='pending') + + def __str__(self): + return f"Order {self.id} - {self.user.username}" + +class OrderItem(models.Model): + order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE) + product = models.ForeignKey(Product, on_delete=models.CASCADE) + quantity = models.PositiveIntegerField(default=1) + price = models.DecimalField(max_digits=10, decimal_places=2) + + def __str__(self): + return f"{self.quantity} x {self.product.title}" + +@receiver(post_save, sender=User) +def create_user_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance) + +@receiver(post_save, sender=User) +def save_user_profile(sender, instance, **kwargs): + instance.profile.save() \ No newline at end of file diff --git a/core/pexels.py b/core/pexels.py new file mode 100644 index 0000000..ff6a876 --- /dev/null +++ b/core/pexels.py @@ -0,0 +1,44 @@ +import os +from pathlib import Path +import httpx + +API_KEY = os.getenv("PEXELS_KEY", "Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18") +CACHE_DIR = Path("static/images/pexels") + +def _client(): + return httpx.Client( + base_url="https://api.pexels.com/v1/", + headers={"Authorization": API_KEY}, + timeout=15, + ) + +def fetch_first(query: str, orientation: str = "portrait") -> dict | None: + if not API_KEY: + return None + try: + with _client() as client: + resp = client.get( + "search", + params={"query": query, "orientation": orientation, "per_page": 1, "page": 1}, + ) + resp.raise_for_status() + data = resp.json() + photo = (data.get("photos") or [None])[0] + if not photo: + return None + src = photo["src"].get("large2x") or photo["src"].get("large") or photo["src"].get("original") + CACHE_DIR.mkdir(parents=True, exist_ok=True) + target = CACHE_DIR / f"{photo['id']}.jpg" + if src: + img = httpx.get(src, timeout=15) + img.raise_for_status() + target.write_bytes(img.content) + return { + "id": photo["id"], + "local_path": f"images/pexels/{photo['id']}.jpg", + "photographer": photo.get("photographer"), + "photographer_url": photo.get("photographer_url"), + } + except Exception as e: + print(f"Error fetching from Pexels: {e}") + return None diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..8379894 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,209 @@ +{% load static %} -
- -