Initial import
0
myproject/accounts/__init__.py
Normal file
BIN
myproject/accounts/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
myproject/accounts/__pycache__/admin.cpython-313.pyc
Normal file
BIN
myproject/accounts/__pycache__/apps.cpython-313.pyc
Normal file
BIN
myproject/accounts/__pycache__/forms.cpython-313.pyc
Normal file
BIN
myproject/accounts/__pycache__/models.cpython-313.pyc
Normal file
BIN
myproject/accounts/__pycache__/tests.cpython-313.pyc
Normal file
BIN
myproject/accounts/__pycache__/urls.cpython-313.pyc
Normal file
BIN
myproject/accounts/__pycache__/views.cpython-313.pyc
Normal file
20
myproject/accounts/admin.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
|
from accounts.models import Profile
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Profile)
|
||||||
|
class ProfileAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('user', 'is_seller', 'image_preview')
|
||||||
|
|
||||||
|
def image_preview(self, obj):
|
||||||
|
if obj.image:
|
||||||
|
return format_html(
|
||||||
|
'<img src="{}" width="40" height="40" '
|
||||||
|
'style="border-radius: 50%; object-fit: cover;" />',
|
||||||
|
obj.image.url
|
||||||
|
)
|
||||||
|
return "No Image"
|
||||||
|
|
||||||
|
image_preview.short_description = 'Image'
|
||||||
5
myproject/accounts/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AccountsConfig(AppConfig):
|
||||||
|
name = 'accounts'
|
||||||
27
myproject/accounts/forms.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from .models import Profile
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileForm(forms.ModelForm):
|
||||||
|
first_name = forms.CharField(required=False, max_length=30)
|
||||||
|
last_name = forms.CharField(required=False, max_length=150)
|
||||||
|
email = forms.EmailField(required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Profile
|
||||||
|
fields = ['image', 'bio']
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
profile = super().save(commit=False)
|
||||||
|
# update related user fields
|
||||||
|
user = profile.user
|
||||||
|
user.first_name = self.cleaned_data.get('first_name', user.first_name)
|
||||||
|
user.last_name = self.cleaned_data.get('last_name', user.last_name)
|
||||||
|
email = self.cleaned_data.get('email')
|
||||||
|
if email:
|
||||||
|
user.email = email
|
||||||
|
if commit:
|
||||||
|
user.save()
|
||||||
|
profile.save()
|
||||||
|
return profile
|
||||||
27
myproject/accounts/migrations/0001_initial.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-18 11:13
|
||||||
|
|
||||||
|
import accounts.models
|
||||||
|
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='Profile',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('bio', models.TextField(blank=True, null=True)),
|
||||||
|
('image', models.ImageField(blank=True, null=True, upload_to=accounts.models.user_profile_upload_path)),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
18
myproject/accounts/migrations/0002_profile_is_seller.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profile',
|
||||||
|
name='is_seller',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
myproject/accounts/migrations/__init__.py
Normal file
31
myproject/accounts/models.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def user_profile_upload_path(instance, filename):
|
||||||
|
# Files will be uploaded to MEDIA_ROOT/profile_pics/user_<id>/<filename>
|
||||||
|
return f'profile_pics/user_{instance.user.id}/{filename}'
|
||||||
|
|
||||||
|
|
||||||
|
class Profile(models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
|
||||||
|
bio = models.TextField(blank=True, null=True)
|
||||||
|
image = models.ImageField(upload_to=user_profile_upload_path, blank=True, null=True)
|
||||||
|
is_seller = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Profile for {self.user.username}'
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=User)
|
||||||
|
def ensure_profile_exists(sender, instance, created, **kwargs):
|
||||||
|
if created:
|
||||||
|
Profile.objects.create(user=instance)
|
||||||
|
else:
|
||||||
|
# save existing profile to ensure any related signals run
|
||||||
|
try:
|
||||||
|
instance.profile.save()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
3
myproject/accounts/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
10
myproject/accounts/urls.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('login/', views.login_view, name='login'),
|
||||||
|
path('register/', views.register_view, name='register'),
|
||||||
|
path('logout/', views.logout_view, name='logout'),
|
||||||
|
path('profile/', views.profile_view, name='profile'),
|
||||||
|
path('profile/edit/', views.edit_profile, name='edit_profile'),
|
||||||
|
]
|
||||||
132
myproject/accounts/views.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db.models import Sum
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
|
||||||
|
from orders.models import Order
|
||||||
|
from products.models import WishlistItem
|
||||||
|
|
||||||
|
|
||||||
|
def login_view(request):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
return redirect('profile')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.POST.get('username', '').strip()
|
||||||
|
password = request.POST.get('password', '').strip()
|
||||||
|
|
||||||
|
if not username or not password:
|
||||||
|
return render(request, 'accounts/login.html', {'error': 'Username and password are required'})
|
||||||
|
|
||||||
|
user = authenticate(request, username=username, password=password)
|
||||||
|
|
||||||
|
if user:
|
||||||
|
login(request, user)
|
||||||
|
messages.success(request, f'Welcome back, {username}!')
|
||||||
|
return redirect('profile')
|
||||||
|
|
||||||
|
return render(request, 'accounts/login.html', {'error': 'Invalid username or password. Please check and try again.'})
|
||||||
|
|
||||||
|
return render(request, 'accounts/login.html')
|
||||||
|
|
||||||
|
|
||||||
|
def register_view(request):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
return redirect('profile')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.POST.get('username', '').strip()
|
||||||
|
password = request.POST.get('password', '').strip()
|
||||||
|
confirm_password = request.POST.get('confirm_password', '').strip()
|
||||||
|
email = request.POST.get('email', '').strip()
|
||||||
|
register_as_seller = request.POST.get('register_as_seller') == 'on'
|
||||||
|
|
||||||
|
if not username or not password or not confirm_password:
|
||||||
|
return render(request, 'accounts/register.html', {'error': 'All fields are required', 'username': username, 'email': email, 'register_as_seller': register_as_seller})
|
||||||
|
if len(username) < 3:
|
||||||
|
return render(request, 'accounts/register.html', {'error': 'Username must be at least 3 characters long', 'username': username, 'email': email, 'register_as_seller': register_as_seller})
|
||||||
|
if len(password) < 6:
|
||||||
|
return render(request, 'accounts/register.html', {'error': 'Password must be at least 6 characters long', 'username': username, 'email': email, 'register_as_seller': register_as_seller})
|
||||||
|
if password != confirm_password:
|
||||||
|
return render(request, 'accounts/register.html', {'error': 'Passwords do not match', 'username': username, 'email': email, 'register_as_seller': register_as_seller})
|
||||||
|
if User.objects.filter(username=username).exists():
|
||||||
|
return render(request, 'accounts/register.html', {'error': 'Username already exists', 'email': email, 'register_as_seller': register_as_seller})
|
||||||
|
if email and User.objects.filter(email=email).exists():
|
||||||
|
return render(request, 'accounts/register.html', {'error': 'Email already registered', 'username': username, 'register_as_seller': register_as_seller})
|
||||||
|
|
||||||
|
user = User.objects.create_user(username=username, password=password, email=email)
|
||||||
|
if register_as_seller:
|
||||||
|
user.profile.is_seller = True
|
||||||
|
user.profile.save(update_fields=['is_seller'])
|
||||||
|
messages.success(request, 'Account created successfully! Please log in.')
|
||||||
|
return redirect('login')
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'accounts/register.html',
|
||||||
|
{'register_as_seller': request.GET.get('seller') == '1'},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def logout_view(request):
|
||||||
|
logout(request)
|
||||||
|
return redirect('/')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def profile_view(request):
|
||||||
|
user_orders = Order.objects.filter(user=request.user)
|
||||||
|
delivered_orders = user_orders.filter(status='Delivered')
|
||||||
|
recent_orders = user_orders.order_by('-created_at')[:5]
|
||||||
|
|
||||||
|
total_spent = delivered_orders.aggregate(total=Sum('total_price')).get('total') or 0
|
||||||
|
wishlist_count = WishlistItem.objects.filter(user=request.user).count()
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'accounts/profile.html',
|
||||||
|
{
|
||||||
|
'user': request.user,
|
||||||
|
'orders_count': user_orders.count(),
|
||||||
|
'delivered_count': delivered_orders.count(),
|
||||||
|
'pending_count': user_orders.exclude(status='Delivered').count(),
|
||||||
|
'wishlist_count': wishlist_count,
|
||||||
|
'total_spent': total_spent,
|
||||||
|
'recent_orders': recent_orders,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_profile(request):
|
||||||
|
from .forms import ProfileForm
|
||||||
|
|
||||||
|
profile = getattr(request.user, 'profile', None)
|
||||||
|
if profile is None:
|
||||||
|
# ensure profile exists
|
||||||
|
from .models import Profile
|
||||||
|
|
||||||
|
profile = Profile.objects.create(user=request.user)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ProfileForm(request.POST, request.FILES, instance=profile)
|
||||||
|
# populate user fields into form for display/save
|
||||||
|
form.fields['first_name'].initial = request.user.first_name
|
||||||
|
form.fields['last_name'].initial = request.user.last_name
|
||||||
|
form.fields['email'].initial = request.user.email
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
messages.success(request, 'Profile updated successfully.')
|
||||||
|
return redirect('profile')
|
||||||
|
else:
|
||||||
|
messages.error(request, 'Please correct the errors below.')
|
||||||
|
else:
|
||||||
|
form = ProfileForm(instance=profile)
|
||||||
|
form.fields['first_name'].initial = request.user.first_name
|
||||||
|
form.fields['last_name'].initial = request.user.last_name
|
||||||
|
form.fields['email'].initial = request.user.email
|
||||||
|
|
||||||
|
return render(request, 'accounts/edit_profile.html', {'form': form, 'profile': profile})
|
||||||
0
myproject/cart/__init__.py
Normal file
BIN
myproject/cart/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
myproject/cart/__pycache__/admin.cpython-313.pyc
Normal file
BIN
myproject/cart/__pycache__/apps.cpython-313.pyc
Normal file
BIN
myproject/cart/__pycache__/models.cpython-313.pyc
Normal file
BIN
myproject/cart/__pycache__/tests.cpython-313.pyc
Normal file
BIN
myproject/cart/__pycache__/urls.cpython-313.pyc
Normal file
BIN
myproject/cart/__pycache__/views.cpython-313.pyc
Normal file
38
myproject/cart/admin.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Cart, CartItem, Coupon
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Coupon)
|
||||||
|
class CouponAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('code', 'discount_percent', 'min_purchase', 'active', 'valid_from', 'valid_to')
|
||||||
|
list_filter = ('active',)
|
||||||
|
search_fields = ('code',)
|
||||||
|
|
||||||
|
|
||||||
|
class CartItemInline(admin.TabularInline):
|
||||||
|
model = CartItem
|
||||||
|
extra = 0
|
||||||
|
autocomplete_fields = ('product',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Cart)
|
||||||
|
class CartAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('user', 'created_at', 'updated_at', 'item_count', 'total_quantity')
|
||||||
|
search_fields = ('user__username', 'user__email')
|
||||||
|
inlines = (CartItemInline,)
|
||||||
|
|
||||||
|
def item_count(self, obj):
|
||||||
|
return obj.items.count()
|
||||||
|
item_count.short_description = 'Items'
|
||||||
|
|
||||||
|
def total_quantity(self, obj):
|
||||||
|
return sum(i.quantity for i in obj.items.all())
|
||||||
|
total_quantity.short_description = 'Total Qty'
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(CartItem)
|
||||||
|
class CartItemAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('cart', 'product', 'quantity')
|
||||||
|
search_fields = ('product__name', 'cart__user__username')
|
||||||
|
autocomplete_fields = ('product',)
|
||||||
5
myproject/cart/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CartConfig(AppConfig):
|
||||||
|
name = 'cart'
|
||||||
39
myproject/cart/migrations/0001_initial.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-18 10:08
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('products', '0004_product_updated_at'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
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)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='cart', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CartItem',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('quantity', models.PositiveIntegerField(default=1)),
|
||||||
|
('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='cart.cart')),
|
||||||
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('cart', 'product')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
28
myproject/cart/migrations/0002_coupon.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-18 10:24
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cart', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Coupon',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('code', models.CharField(max_length=32, unique=True)),
|
||||||
|
('discount_percent', models.DecimalField(decimal_places=2, max_digits=5)),
|
||||||
|
('min_purchase', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
|
||||||
|
('active', models.BooleanField(default=True)),
|
||||||
|
('valid_from', models.DateTimeField(blank=True, null=True)),
|
||||||
|
('valid_to', models.DateTimeField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['code'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
myproject/cart/migrations/__init__.py
Normal file
BIN
myproject/cart/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
40
myproject/cart/models.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from products.models import Product
|
||||||
|
|
||||||
|
|
||||||
|
class Cart(models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='cart')
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.username}'s cart"
|
||||||
|
|
||||||
|
|
||||||
|
class CartItem(models.Model):
|
||||||
|
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, related_name='items')
|
||||||
|
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
||||||
|
quantity = models.PositiveIntegerField(default=1)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('cart', 'product')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.product.name} x {self.quantity}"
|
||||||
|
|
||||||
|
|
||||||
|
class Coupon(models.Model):
|
||||||
|
code = models.CharField(max_length=32, unique=True)
|
||||||
|
discount_percent = models.DecimalField(max_digits=5, decimal_places=2)
|
||||||
|
min_purchase = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||||||
|
active = models.BooleanField(default=True)
|
||||||
|
valid_from = models.DateTimeField(null=True, blank=True)
|
||||||
|
valid_to = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['code']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.code
|
||||||
3
myproject/cart/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
13
myproject/cart/urls.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.cart_view, name='cart'),
|
||||||
|
path('add/<int:id>/', views.add_to_cart, name='add_to_cart'),
|
||||||
|
path('buy-now/<int:id>/', views.buy_now, name='buy_now'),
|
||||||
|
path('update/<int:id>/', views.update_cart, name='update_cart'),
|
||||||
|
path('remove/<int:id>/', views.remove_from_cart, name='remove_from_cart'),
|
||||||
|
path('coupon/apply/', views.apply_coupon, name='apply_coupon'),
|
||||||
|
path('coupon/remove/', views.remove_coupon, name='remove_coupon'),
|
||||||
|
]
|
||||||
159
myproject/cart/views.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from products.models import Product
|
||||||
|
|
||||||
|
from .models import Coupon
|
||||||
|
|
||||||
|
|
||||||
|
def get_cart(request):
|
||||||
|
return request.session.get('cart', {})
|
||||||
|
|
||||||
|
|
||||||
|
def save_cart(request, cart):
|
||||||
|
request.session['cart'] = cart
|
||||||
|
request.session.modified = True
|
||||||
|
|
||||||
|
|
||||||
|
def _redirect_after_add(request):
|
||||||
|
next_url = request.GET.get('next', '').strip()
|
||||||
|
if next_url.startswith('/'):
|
||||||
|
return redirect(next_url)
|
||||||
|
return redirect('cart')
|
||||||
|
|
||||||
|
|
||||||
|
def add_to_cart(request, id):
|
||||||
|
product = get_object_or_404(Product, id=id)
|
||||||
|
cart = get_cart(request)
|
||||||
|
current_qty = cart.get(str(id), 0)
|
||||||
|
|
||||||
|
if current_qty + 1 > product.stock:
|
||||||
|
messages.warning(request, f'Only {product.stock} units of {product.name} are available.')
|
||||||
|
else:
|
||||||
|
cart[str(id)] = current_qty + 1
|
||||||
|
messages.success(request, f'Added {product.name} to your cart.')
|
||||||
|
|
||||||
|
save_cart(request, cart)
|
||||||
|
return _redirect_after_add(request)
|
||||||
|
|
||||||
|
|
||||||
|
def buy_now(request, id):
|
||||||
|
product = get_object_or_404(Product, id=id)
|
||||||
|
if product.stock <= 0:
|
||||||
|
messages.error(request, f'{product.name} is out of stock.')
|
||||||
|
return redirect('product_detail', product_id=product.id)
|
||||||
|
|
||||||
|
request.session['buy_now'] = {str(product.id): 1}
|
||||||
|
request.session.modified = True
|
||||||
|
messages.info(request, f'Proceeding to checkout for {product.name}.')
|
||||||
|
return redirect('checkout')
|
||||||
|
|
||||||
|
|
||||||
|
def update_cart(request, id):
|
||||||
|
if request.method == 'POST':
|
||||||
|
qty = int(request.POST.get('quantity', 1))
|
||||||
|
product = get_object_or_404(Product, id=id)
|
||||||
|
cart = get_cart(request)
|
||||||
|
|
||||||
|
if qty <= 0:
|
||||||
|
cart.pop(str(id), None)
|
||||||
|
messages.info(request, f'{product.name} removed from cart.')
|
||||||
|
elif qty > product.stock:
|
||||||
|
cart[str(id)] = product.stock
|
||||||
|
messages.warning(request, f'Quantity adjusted to {product.stock} for {product.name}.')
|
||||||
|
else:
|
||||||
|
cart[str(id)] = qty
|
||||||
|
messages.success(request, f'Cart updated for {product.name}.')
|
||||||
|
|
||||||
|
save_cart(request, cart)
|
||||||
|
|
||||||
|
return redirect('cart')
|
||||||
|
|
||||||
|
|
||||||
|
def remove_from_cart(request, id):
|
||||||
|
cart = get_cart(request)
|
||||||
|
if str(id) in cart:
|
||||||
|
del cart[str(id)]
|
||||||
|
messages.info(request, 'Item removed from your cart.')
|
||||||
|
save_cart(request, cart)
|
||||||
|
return redirect('cart')
|
||||||
|
|
||||||
|
|
||||||
|
def _get_coupon_for_cart(code, subtotal):
|
||||||
|
if not code:
|
||||||
|
return None, Decimal('0')
|
||||||
|
|
||||||
|
now = timezone.now()
|
||||||
|
coupon = Coupon.objects.filter(code=code, active=True).first()
|
||||||
|
if not coupon:
|
||||||
|
return None, Decimal('0')
|
||||||
|
|
||||||
|
if coupon.valid_from and now < coupon.valid_from:
|
||||||
|
return None, Decimal('0')
|
||||||
|
if coupon.valid_to and now > coupon.valid_to:
|
||||||
|
return None, Decimal('0')
|
||||||
|
if subtotal < coupon.min_purchase:
|
||||||
|
return coupon, Decimal('0')
|
||||||
|
|
||||||
|
discount = (subtotal * (coupon.discount_percent / Decimal('100'))).quantize(Decimal('0.01'))
|
||||||
|
return coupon, discount
|
||||||
|
|
||||||
|
|
||||||
|
def apply_coupon(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
code = request.POST.get('coupon_code', '').strip().upper()
|
||||||
|
request.session['coupon_code'] = code
|
||||||
|
messages.success(request, f'Coupon {code} applied.')
|
||||||
|
return redirect('cart')
|
||||||
|
|
||||||
|
|
||||||
|
def remove_coupon(request):
|
||||||
|
request.session.pop('coupon_code', None)
|
||||||
|
messages.info(request, 'Coupon removed.')
|
||||||
|
return redirect('cart')
|
||||||
|
|
||||||
|
|
||||||
|
def cart_view(request):
|
||||||
|
cart = get_cart(request)
|
||||||
|
products = []
|
||||||
|
subtotal = Decimal('0')
|
||||||
|
|
||||||
|
for id, qty in cart.items():
|
||||||
|
product = get_object_or_404(Product, id=int(id))
|
||||||
|
product.qty = qty
|
||||||
|
product.subtotal = product.display_price * qty
|
||||||
|
subtotal += product.subtotal
|
||||||
|
products.append(product)
|
||||||
|
|
||||||
|
shipping = Decimal('60') if products else Decimal('0')
|
||||||
|
|
||||||
|
coupon_code = request.session.get('coupon_code', '')
|
||||||
|
coupon, discount = _get_coupon_for_cart(coupon_code, subtotal)
|
||||||
|
if coupon_code and not coupon:
|
||||||
|
request.session.pop('coupon_code', None)
|
||||||
|
messages.warning(request, 'Coupon is invalid or expired.')
|
||||||
|
coupon_code = ''
|
||||||
|
|
||||||
|
if coupon and subtotal < coupon.min_purchase:
|
||||||
|
messages.warning(request, f'Coupon requires minimum purchase of Rs. {coupon.min_purchase}.')
|
||||||
|
discount = Decimal('0')
|
||||||
|
|
||||||
|
grand_total = subtotal - discount + shipping
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'cart/cart.html',
|
||||||
|
{
|
||||||
|
'products': products,
|
||||||
|
'subtotal': subtotal,
|
||||||
|
'total': subtotal,
|
||||||
|
'shipping': shipping,
|
||||||
|
'discount': discount,
|
||||||
|
'grand_total': grand_total,
|
||||||
|
'coupon_code': coupon_code,
|
||||||
|
'coupon': coupon,
|
||||||
|
},
|
||||||
|
)
|
||||||
0
myproject/core/__init__.py
Normal file
BIN
myproject/core/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
myproject/core/__pycache__/admin.cpython-313.pyc
Normal file
BIN
myproject/core/__pycache__/apps.cpython-313.pyc
Normal file
BIN
myproject/core/__pycache__/context_processors.cpython-313.pyc
Normal file
BIN
myproject/core/__pycache__/models.cpython-313.pyc
Normal file
BIN
myproject/core/__pycache__/tests.cpython-313.pyc
Normal file
BIN
myproject/core/__pycache__/urls.cpython-313.pyc
Normal file
BIN
myproject/core/__pycache__/views.cpython-313.pyc
Normal file
3
myproject/core/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
5
myproject/core/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CoreConfig(AppConfig):
|
||||||
|
name = 'core'
|
||||||
159
myproject/core/context_processors.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
from orders.models import Order
|
||||||
|
from products.models import Product
|
||||||
|
|
||||||
|
CATEGORY_ICONS = {
|
||||||
|
'fashion': '👕',
|
||||||
|
'mobile': '📱',
|
||||||
|
'mobiles': '📱',
|
||||||
|
'electronics': '💻',
|
||||||
|
'home': '🛋️',
|
||||||
|
'appliances': '📺',
|
||||||
|
'toys': '🧸',
|
||||||
|
'beauty': '💄',
|
||||||
|
'books': '📚',
|
||||||
|
'sports': '🏀',
|
||||||
|
'groceries': '🛒',
|
||||||
|
'health': '💊',
|
||||||
|
'jewelry': '💍',
|
||||||
|
'home decor': '🕯️',
|
||||||
|
'stationery': '📝',
|
||||||
|
'pets': '🐾',
|
||||||
|
'accessories': '🧢',
|
||||||
|
'shoes': '👟',
|
||||||
|
'kids': '🧒',
|
||||||
|
'tools': '🛠️',
|
||||||
|
'office': '📎',
|
||||||
|
'kitchen': '🍳',
|
||||||
|
'travel': '✈️',
|
||||||
|
'automotive': '🚗',
|
||||||
|
'garden': '🌿',
|
||||||
|
'party': '🎉',
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_CATEGORIES = [
|
||||||
|
{'value': 'fashion', 'name': 'Fashion', 'icon': '👕'},
|
||||||
|
{'value': 'mobiles', 'name': 'Mobiles', 'icon': '📱'},
|
||||||
|
{'value': 'electronics', 'name': 'Electronics', 'icon': '💻'},
|
||||||
|
{'value': 'home', 'name': 'Home', 'icon': '🛋️'},
|
||||||
|
{'value': 'appliances', 'name': 'Appliances', 'icon': '📺'},
|
||||||
|
{'value': 'toys', 'name': 'Toys', 'icon': '🧸'},
|
||||||
|
{'value': 'beauty', 'name': 'Beauty', 'icon': '💄'},
|
||||||
|
{'value': 'books', 'name': 'Books', 'icon': '📚'},
|
||||||
|
{'value': 'sports', 'name': 'Sports', 'icon': '🏀'},
|
||||||
|
{'value': 'groceries', 'name': 'Groceries', 'icon': '🛒'},
|
||||||
|
{'value': 'health', 'name': 'Health', 'icon': '💊'},
|
||||||
|
{'value': 'jewelry', 'name': 'Jewelry', 'icon': '💍'},
|
||||||
|
{'value': 'pets', 'name': 'Pets', 'icon': '🐾'},
|
||||||
|
{'value': 'shoes', 'name': 'Shoes', 'icon': '👟'},
|
||||||
|
]
|
||||||
|
|
||||||
|
EN_LABELS = {
|
||||||
|
'home': 'Home',
|
||||||
|
'products': 'Products',
|
||||||
|
'all_products': 'All Products',
|
||||||
|
'featured': 'Featured',
|
||||||
|
'categories': 'Categories',
|
||||||
|
'cart': 'Cart',
|
||||||
|
'wishlist': 'Wishlist',
|
||||||
|
'about': 'About',
|
||||||
|
'support': 'Help & Support',
|
||||||
|
'orders': 'Orders',
|
||||||
|
'my_profile': 'My Profile',
|
||||||
|
'my_orders': 'My Orders',
|
||||||
|
'login': 'Login',
|
||||||
|
'register': 'Register',
|
||||||
|
'logout': 'Logout',
|
||||||
|
'shop_now': 'Shop Now',
|
||||||
|
'learn_more': 'Learn More',
|
||||||
|
'choose_language': 'Language',
|
||||||
|
'english': 'English',
|
||||||
|
'nepali': 'Nepali',
|
||||||
|
'theme_toggle': 'Light Mode',
|
||||||
|
'for_you': 'For You',
|
||||||
|
}
|
||||||
|
|
||||||
|
NE_LABELS = {
|
||||||
|
'home': 'Griha',
|
||||||
|
'products': 'Utpadan',
|
||||||
|
'all_products': 'Sabai Utpadan',
|
||||||
|
'featured': 'Bisesh',
|
||||||
|
'categories': 'Shreni',
|
||||||
|
'cart': 'Kart',
|
||||||
|
'wishlist': 'Icchha Suchi',
|
||||||
|
'about': 'Hamro Barema',
|
||||||
|
'support': 'Sahayog',
|
||||||
|
'orders': 'Orderharu',
|
||||||
|
'my_profile': 'Mero Profile',
|
||||||
|
'my_orders': 'Mero Order',
|
||||||
|
'login': 'Login',
|
||||||
|
'register': 'Darta',
|
||||||
|
'logout': 'Logout',
|
||||||
|
'shop_now': 'Ahile Kinmel',
|
||||||
|
'learn_more': 'Thap Jankari',
|
||||||
|
'choose_language': 'Bhasha',
|
||||||
|
'for_you': 'Tapaiko Lagi',
|
||||||
|
'english': 'English',
|
||||||
|
'nepali': 'Nepali',
|
||||||
|
'theme_toggle': 'Light Mode',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def cart_summary(request):
|
||||||
|
cart = request.session.get('cart', {})
|
||||||
|
total_items = sum(cart.values()) if isinstance(cart, dict) else 0
|
||||||
|
order_count = 0
|
||||||
|
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
order_count = Order.objects.filter(user=request.user).count()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'cart_count': total_items,
|
||||||
|
'order_count': order_count,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def language_context(request):
|
||||||
|
site_language = request.session.get('site_language', 'en')
|
||||||
|
if site_language not in {'en', 'ne'}:
|
||||||
|
site_language = 'en'
|
||||||
|
|
||||||
|
ui = NE_LABELS if site_language == 'ne' else EN_LABELS
|
||||||
|
|
||||||
|
raw_categories = Product.objects.values_list('category', flat=True).exclude(category='').distinct()
|
||||||
|
category_items = []
|
||||||
|
seen = set()
|
||||||
|
for raw_value in raw_categories:
|
||||||
|
cleaned = (raw_value or '').strip()
|
||||||
|
if not cleaned:
|
||||||
|
continue
|
||||||
|
key = cleaned.lower()
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
category_items.append({
|
||||||
|
'value': cleaned,
|
||||||
|
'name': cleaned.title(),
|
||||||
|
'icon': CATEGORY_ICONS.get(key, '🛍️'),
|
||||||
|
})
|
||||||
|
|
||||||
|
for default_category in DEFAULT_CATEGORIES:
|
||||||
|
if default_category['value'].lower() not in seen:
|
||||||
|
category_items.append(default_category)
|
||||||
|
seen.add(default_category['value'].lower())
|
||||||
|
|
||||||
|
delivery_location = request.session.get('delivery_location', '').strip()
|
||||||
|
if not delivery_location and request.user.is_authenticated:
|
||||||
|
last_address = Order.objects.filter(user=request.user).exclude(address='').order_by('-created_at').values_list('address', flat=True).first()
|
||||||
|
if last_address:
|
||||||
|
delivery_location = last_address.split('\n', 1)[0].strip()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'site_language': site_language,
|
||||||
|
'ui': ui,
|
||||||
|
'language_options': [
|
||||||
|
{'code': 'en', 'label': ui['english']},
|
||||||
|
{'code': 'ne', 'label': ui['nepali']},
|
||||||
|
],
|
||||||
|
'categories': category_items,
|
||||||
|
'delivery_location': delivery_location,
|
||||||
|
}
|
||||||
0
myproject/core/migrations/__init__.py
Normal file
BIN
myproject/core/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
3
myproject/core/models.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
myproject/core/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
12
myproject/core/urls.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.home, name='home'),
|
||||||
|
path('home/', views.home, name='home'),
|
||||||
|
path('about/', views.about, name='about'),
|
||||||
|
path('support/', views.support, name='support'),
|
||||||
|
path('set-language/', views.set_language_preference, name='set_language_preference'),
|
||||||
|
path('landing/', views.landing, name='landing'),
|
||||||
|
path('settings/', views.settings_view, name='settings'),
|
||||||
|
]
|
||||||
139
myproject/core/views.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.db.models import Avg
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
|
||||||
|
from products.models import Product
|
||||||
|
|
||||||
|
|
||||||
|
def _format_discount_offer(product):
|
||||||
|
if not product or product.price == 0 or product.discount_price is None:
|
||||||
|
return None
|
||||||
|
savings = product.price - product.discount_price
|
||||||
|
percent = int((savings / product.price * Decimal('100')).quantize(Decimal('1')))
|
||||||
|
return {
|
||||||
|
'title': f'{percent}% off',
|
||||||
|
'description': f'{product.name} now available for Rs. {product.discount_price:.0f}',
|
||||||
|
'note': f'Save Rs. {savings:.0f} on {product.category}',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def home(request):
|
||||||
|
trending_products = Product.objects.filter(featured=True).order_by('-created_at')[:4]
|
||||||
|
if not trending_products.exists():
|
||||||
|
trending_products = Product.objects.order_by('-created_at')[:4]
|
||||||
|
|
||||||
|
aggregates = Product.objects.aggregate(avg_rating=Avg('rating'))
|
||||||
|
discounted_products = list(Product.objects.filter(discount_price__isnull=False).order_by('discount_price')[:6])
|
||||||
|
best_discount = None
|
||||||
|
if discounted_products:
|
||||||
|
best_discount = max(
|
||||||
|
discounted_products,
|
||||||
|
key=lambda p: ((p.price - p.discount_price) / p.price) if p.price else Decimal('0'),
|
||||||
|
)
|
||||||
|
|
||||||
|
featured_count = Product.objects.filter(featured=True).count()
|
||||||
|
categories_count = Product.objects.values('category').exclude(category='').distinct().count()
|
||||||
|
|
||||||
|
special_deals = []
|
||||||
|
offer_deal = _format_discount_offer(best_discount)
|
||||||
|
if offer_deal:
|
||||||
|
special_deals.append(offer_deal)
|
||||||
|
|
||||||
|
special_deals.append({
|
||||||
|
'title': f'{len(discounted_products)} offers live',
|
||||||
|
'description': 'Discounts available across top Nepali categories.',
|
||||||
|
'note': 'Browse products with extra savings today.',
|
||||||
|
})
|
||||||
|
|
||||||
|
if featured_count:
|
||||||
|
special_deals.append({
|
||||||
|
'title': 'Featured Seller Picks',
|
||||||
|
'description': f'{featured_count} curated products from trusted sellers.',
|
||||||
|
'note': 'Popular with Nepali shoppers.',
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
special_deals.append({
|
||||||
|
'title': 'Daily Deals',
|
||||||
|
'description': 'Fresh discount offers updated every day.',
|
||||||
|
'note': 'Check back for more savings.',
|
||||||
|
})
|
||||||
|
|
||||||
|
advertised_products = sorted(
|
||||||
|
discounted_products,
|
||||||
|
key=lambda p: ((p.price - p.discount_price) / p.price if p.price else Decimal('0')),
|
||||||
|
reverse=True,
|
||||||
|
)[:4]
|
||||||
|
|
||||||
|
if not advertised_products:
|
||||||
|
advertised_products = list(trending_products[:4])
|
||||||
|
|
||||||
|
for index, product in enumerate(advertised_products):
|
||||||
|
savings = (product.price - product.discount_price) if product.discount_price else Decimal('0')
|
||||||
|
percent = int((savings / product.price * Decimal('100')).quantize(Decimal('1'))) if product.price else 0
|
||||||
|
if percent >= 50:
|
||||||
|
label = 'Mega Deal'
|
||||||
|
elif percent >= 30:
|
||||||
|
label = 'Hot Deal'
|
||||||
|
elif percent >= 15:
|
||||||
|
label = 'Limited Time'
|
||||||
|
else:
|
||||||
|
label = 'Special Offer'
|
||||||
|
if product.featured and percent >= 10:
|
||||||
|
label = 'Featured Deal'
|
||||||
|
product.deal_label = label
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'core/home.html',
|
||||||
|
{
|
||||||
|
'trending_products': trending_products,
|
||||||
|
'total_products': Product.objects.count(),
|
||||||
|
'featured_products': featured_count,
|
||||||
|
'categories_count': categories_count,
|
||||||
|
'avg_rating': aggregates.get('avg_rating') or 0,
|
||||||
|
'special_deals': special_deals,
|
||||||
|
'advertised_products': advertised_products,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def about(request):
|
||||||
|
return render(request, 'core/about.html')
|
||||||
|
|
||||||
|
|
||||||
|
def support(request):
|
||||||
|
return render(request, 'core/support.html')
|
||||||
|
|
||||||
|
|
||||||
|
def set_language_preference(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
language = request.POST.get('language', 'en').strip().lower()
|
||||||
|
next_url = request.POST.get('next', '/').strip()
|
||||||
|
else:
|
||||||
|
language = request.GET.get('language', 'en').strip().lower()
|
||||||
|
next_url = request.GET.get('next', '/').strip()
|
||||||
|
|
||||||
|
if language not in {'en', 'ne'}:
|
||||||
|
language = 'en'
|
||||||
|
|
||||||
|
request.session['site_language'] = language
|
||||||
|
if next_url.startswith('/'):
|
||||||
|
return redirect(next_url)
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
|
||||||
|
def landing(request):
|
||||||
|
return render(request, 'core/landing.html')
|
||||||
|
|
||||||
|
|
||||||
|
def settings_view(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
delivery_location = request.POST.get('delivery_location', '').strip()
|
||||||
|
if delivery_location:
|
||||||
|
request.session['delivery_location'] = delivery_location
|
||||||
|
else:
|
||||||
|
request.session.pop('delivery_location', None)
|
||||||
|
return redirect('settings')
|
||||||
|
|
||||||
|
return render(request, 'core/settings.html')
|
||||||
BIN
myproject/db.sqlite3
Normal file
22
myproject/manage.py
Normal file
@ -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', 'myproject.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()
|
||||||
BIN
myproject/media/products/1000195715.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
myproject/media/products/16sep-apple-iphone-17-pro1.webp
Normal file
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 112 KiB |
BIN
myproject/media/products/OIP.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
myproject/media/products/balen_ches.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
myproject/media/products/c06424817_1750x1285.avif
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
myproject/media/products/hl_yak_you.webp
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
myproject/media/products/msl53vqmf4xb1.jpg
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
myproject/media/profile_pics/user_1/samanya2_you.webp
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
0
myproject/myproject/__init__.py
Normal file
BIN
myproject/myproject/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
myproject/myproject/__pycache__/settings.cpython-313.pyc
Normal file
BIN
myproject/myproject/__pycache__/urls.cpython-313.pyc
Normal file
BIN
myproject/myproject/__pycache__/wsgi.cpython-313.pyc
Normal file
16
myproject/myproject/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for myproject 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/6.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
136
myproject/myproject/settings.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
"""
|
||||||
|
Django settings for myproject project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 6.0.5.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/6.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/6.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 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/6.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-%)ie75*7@1xl91^h6^$d!npm$=cf7@oqcc9b&jqxqc8t!@9uj1'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['localhost', '127.0.0.1', '[::1]', 'testserver']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'core', # core app for home, about, contact
|
||||||
|
'accounts', # accounts app for user profiles
|
||||||
|
'cart', # cart app for shopping cart functionality
|
||||||
|
'orders', # orders app for order management
|
||||||
|
'products', # products app for product management
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
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',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'myproject.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [BASE_DIR / 'tempelates'], # ✅ FIX HERE
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
'core.context_processors.cart_summary',
|
||||||
|
'core.context_processors.language_context',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'myproject.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/6.0/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',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
MEDIA_ROOT = BASE_DIR / 'media'
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/6.0/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/6.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / "static",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Authentication Settings
|
||||||
|
LOGIN_URL = 'login'
|
||||||
|
LOGIN_REDIRECT_URL = 'profile'
|
||||||
|
LOGOUT_REDIRECT_URL = 'home'
|
||||||
20
myproject/myproject/urls.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
|
path('', include('core.urls')), # home, about, contact
|
||||||
|
# profile system
|
||||||
|
path('accounts/', include('accounts.urls')),
|
||||||
|
path('products/', include('products.urls')),
|
||||||
|
path('cart/', include('cart.urls')),
|
||||||
|
path('orders/', include('orders.urls')),
|
||||||
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS[0])
|
||||||
16
myproject/myproject/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for myproject 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/6.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
0
myproject/orders/__init__.py
Normal file
BIN
myproject/orders/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
myproject/orders/__pycache__/admin.cpython-313.pyc
Normal file
BIN
myproject/orders/__pycache__/apps.cpython-313.pyc
Normal file
BIN
myproject/orders/__pycache__/models.cpython-313.pyc
Normal file
BIN
myproject/orders/__pycache__/tests.cpython-313.pyc
Normal file
BIN
myproject/orders/__pycache__/urls.cpython-313.pyc
Normal file
BIN
myproject/orders/__pycache__/views.cpython-313.pyc
Normal file
19
myproject/orders/admin.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Order, OrderItem
|
||||||
|
|
||||||
|
|
||||||
|
class OrderItemInline(admin.TabularInline):
|
||||||
|
model = OrderItem
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Order)
|
||||||
|
class OrderAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id', 'user', 'status', 'payment_method', 'total_price', 'created_at')
|
||||||
|
list_filter = ('status', 'payment_method')
|
||||||
|
search_fields = ('user__username', 'id')
|
||||||
|
inlines = [OrderItemInline]
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(OrderItem)
|
||||||
5
myproject/orders/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class OrdersConfig(AppConfig):
|
||||||
|
name = 'orders'
|
||||||
37
myproject/orders/migrations/0001_initial.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-15 02:32
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('products', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Order',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('total_price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrderItem',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('quantity', models.IntegerField()),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='orders.order')),
|
||||||
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-15 10:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orders', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='payment_method',
|
||||||
|
field=models.CharField(default='COD', max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('Pending', 'Pending'), ('Paid', 'Paid'), ('Shipped', 'Shipped'), ('Delivered', 'Delivered')], default='Pending', max_length=20),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderitem',
|
||||||
|
name='quantity',
|
||||||
|
field=models.PositiveIntegerField(default=1),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
myproject/orders/migrations/0003_alter_order_options.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-18 10:08
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orders', '0002_order_payment_method_order_status_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='order',
|
||||||
|
options={'ordering': ['-created_at']},
|
||||||
|
),
|
||||||
|
]
|
||||||
28
myproject/orders/migrations/0004_order_delivery_fields.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-05-19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orders', '0003_alter_order_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='address',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='full_name',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=150),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=30),
|
||||||
|
),
|
||||||
|
]
|
||||||