226 lines
8.0 KiB
Python
226 lines
8.0 KiB
Python
from decimal import Decimal
|
|
|
|
from django.contrib import messages
|
|
from django.db.models import Avg, Count, Q
|
|
from django.shortcuts import redirect, render
|
|
|
|
from accounts.forms import DeliveryPreferencesForm
|
|
from accounts.models import Profile
|
|
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 _build_category_spotlights():
|
|
top_categories = list(
|
|
Product.objects.exclude(category='')
|
|
.values('category')
|
|
.annotate(
|
|
total=Count('id'),
|
|
featured_total=Count('id', filter=Q(featured=True)),
|
|
avg_rating=Avg('rating'),
|
|
)
|
|
.order_by('-total', 'category')[:4]
|
|
)
|
|
|
|
for item in top_categories:
|
|
category_products = Product.objects.filter(category=item['category'])
|
|
item['avg_rating'] = item.get('avg_rating') or 0
|
|
item['best_price'] = min((product.display_price for product in category_products), default=Decimal('0'))
|
|
|
|
return top_categories
|
|
|
|
|
|
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 product in 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
|
|
|
|
experience_highlights = [
|
|
{
|
|
'title': 'Verified payments',
|
|
'description': 'Orders are only marked paid after wallet or card verification is completed on the server.',
|
|
'badge': 'Trusted checkout',
|
|
},
|
|
{
|
|
'title': 'Flexible payment flow',
|
|
'description': 'Customers can start with cash on delivery and later switch to eSewa or Khalti before shipment.',
|
|
'badge': 'Wallet ready',
|
|
},
|
|
{
|
|
'title': 'Smart product discovery',
|
|
'description': 'Search, categories, sorting, wishlist, and featured filters make it easier to find the right item fast.',
|
|
'badge': 'Fast browsing',
|
|
},
|
|
{
|
|
'title': 'Trackable orders',
|
|
'description': 'Order status, payment status, and next actions are visible from one clean dashboard-style experience.',
|
|
'badge': 'Clear visibility',
|
|
},
|
|
]
|
|
|
|
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,
|
|
'category_spotlights': _build_category_spotlights(),
|
|
'experience_highlights': experience_highlights,
|
|
},
|
|
)
|
|
|
|
|
|
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):
|
|
location_form = None
|
|
guest_delivery_location = request.session.get('delivery_location', '').strip()
|
|
profile = None
|
|
|
|
if request.user.is_authenticated:
|
|
profile = getattr(request.user, 'profile', None)
|
|
if profile is None:
|
|
profile = Profile.objects.create(user=request.user)
|
|
|
|
if request.method == 'POST':
|
|
location_form = DeliveryPreferencesForm(request.POST, instance=profile)
|
|
if location_form.is_valid():
|
|
profile = location_form.save()
|
|
if profile.short_location:
|
|
request.session['delivery_location'] = profile.short_location
|
|
else:
|
|
request.session.pop('delivery_location', None)
|
|
messages.success(request, 'Delivery preferences updated successfully.')
|
|
return redirect('settings')
|
|
messages.error(request, 'Please correct the location details below.')
|
|
else:
|
|
location_form = DeliveryPreferencesForm(instance=profile)
|
|
elif request.method == 'POST':
|
|
guest_delivery_location = request.POST.get('delivery_location', '').strip()
|
|
if guest_delivery_location:
|
|
request.session['delivery_location'] = guest_delivery_location
|
|
messages.success(request, 'Temporary delivery location saved for this browser.')
|
|
else:
|
|
request.session.pop('delivery_location', None)
|
|
messages.info(request, 'Temporary delivery location cleared.')
|
|
return redirect('settings')
|
|
|
|
return render(
|
|
request,
|
|
'core/settings.html',
|
|
{
|
|
'location_form': location_form,
|
|
'guest_delivery_location': guest_delivery_location,
|
|
'location_saved_at': getattr(profile, 'location_updated_at', None),
|
|
},
|
|
)
|