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),
|
||||
),
|
||||
]
|
||||