from django.contrib import admin from django.shortcuts import render, redirect from django.utils.html import format_html from django.urls import path from django.http import HttpResponseRedirect from django import forms from .models import ( Product, Color, Size, Order, OrderItem, GuestUser, Coupon, CouponUsage, OrderStatus, ShippingAddress ) @admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display = ("title", "ordering_value", "price", "cut") search_fields = ["title", "description"] # Enable search for autocomplete @admin.register(Color) class ColorAdmin(admin.ModelAdmin): list_display = ("color_preview", "name", "ordering", "colorHex", "secondary_hex_color") list_editable = ("ordering",) ordering = ["ordering"] search_fields = ["name"] list_per_page = 20 def color_preview(self, obj): if obj.secondary_hex_color: return format_html( '
', obj.colorHex, obj.secondary_hex_color ) return format_html( '
', obj.colorHex ) @admin.register(Size) class SizeAdmin(admin.ModelAdmin): list_display = ("name",) class OrderItemInline(admin.TabularInline): model = OrderItem extra = 1 # Show one extra row for adding new items autocomplete_fields = ['product'] # Enable product search fields = ('product', 'quantity', 'color', 'size', 'price') @admin.register(OrderItem) class OrderItemAdmin(admin.ModelAdmin): list_display = ('order', 'product', 'quantity', 'color', 'size', 'price', 'get_total_price') list_filter = ('product', 'color', 'size', 'order__status') search_fields = ('order__id', 'product__title', 'order__user__email', 'order__guest_user__email') autocomplete_fields = ['order', 'product'] list_editable = ('quantity', 'price') def get_total_price(self, obj): return obj.get_total_price() get_total_price.short_description = 'Total Price' get_total_price.admin_order_field = 'price' # Allows column to be sortable @admin.register(ShippingAddress) class ShippingAddressAdmin(admin.ModelAdmin): list_display = ('street_address', 'city', 'postal_code', 'country') search_fields = ('street_address', 'city', 'postal_code', 'country') class ChangeOrderStatusForm(forms.Form): _selected_action = forms.CharField(widget=forms.MultipleHiddenInput) status = forms.ChoiceField(choices=OrderStatus.choices, label="New Status") @admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display = ('id', 'get_email', 'date_ordered', 'status', 'total_price', 'get_shipping_address') inlines = [OrderItemInline] list_filter = ('status', 'payment_status') readonly_fields = ('shipping_address_details',) actions = ['change_order_status'] autocomplete_fields = ['user'] # Add this line for user search functionality search_fields = ['id', 'user__email', 'user__username', 'guest_user__email'] # Add this line def get_email(self, obj): if obj.guest_user: return obj.guest_user.email else: return obj.user.email get_email.short_description = 'Email' def get_shipping_address(self, obj): if obj.shipping_address: return f"{obj.shipping_address.street_address}, {obj.shipping_address.city}" return "No shipping address" get_shipping_address.short_description = 'Shipping Address' def shipping_address_details(self, obj): if obj.shipping_address: return format_html( """
Street: {}
{} City: {}
State: {}
Postal Code: {}
Country: {}
""", obj.shipping_address.street_address, f"Apartment: {obj.shipping_address.apartment}
" if obj.shipping_address.apartment else "", obj.shipping_address.city, obj.shipping_address.state, obj.shipping_address.postal_code, obj.shipping_address.country, ) return "No shipping address set" shipping_address_details.short_description = 'Shipping Address Details' fieldsets = ( (None, { 'fields': ('user', 'guest_user', 'status', 'payment_status', 'total_price') }), ('Shipping Information', { 'fields': ('shipping_address_details',), }), ('Payment Details', { 'fields': ('stripe_payment_intent_id', 'stripe_checkout_session_id', 'stripe_mode'), 'classes': ('collapse',) }), ('Discount Information', { 'fields': ('coupon', 'discount_amount'), 'classes': ('collapse',) }), ) def changelist_view(self, request, extra_context=None): # If 'show_preparation' parameter is in the request, show the preparation view if 'show_preparation' in request.GET: return self.preparation_view(request) # Otherwise show the normal change list extra_context = extra_context or {} paid_orders_count = Order.objects.filter(status=OrderStatus.PAID).count() extra_context['paid_orders_count'] = paid_orders_count return super().changelist_view(request, extra_context=extra_context) def preparation_view(self, request): """View for items that need to be prepared""" # Get paid orders orders = Order.objects.filter(status=OrderStatus.PAID).order_by('-date_ordered') # Group items by product, color, size items_by_variant = {} all_items = OrderItem.objects.filter(order__status=OrderStatus.PAID) for item in all_items: # Create a key for grouping items key = ( str(item.product.id), str(item.color.id) if item.color else 'none', str(item.size.id) if item.size else 'none' ) if key not in items_by_variant: items_by_variant[key] = { 'product': item.product, 'color': item.color, 'size': item.size, 'quantity': 0, 'orders': set() } items_by_variant[key]['quantity'] += item.quantity items_by_variant[key]['orders'].add(item.order.id) # Convert to list and sort items_list = list(items_by_variant.values()) items_list.sort(key=lambda x: x['product'].title) context = { 'title': 'Orders to Prepare', 'app_label': 'shop', 'opts': Order._meta, 'orders': orders, 'items': items_list, 'total_orders': orders.count(), 'total_items': sum(i['quantity'] for i in items_list) } return render( request, 'admin/shop/order/preparation_view.html', context ) def get_urls(self): urls = super().get_urls() custom_urls = [ path('prepare-all-orders/', self.admin_site.admin_view(self.prepare_all_orders), name='prepare_all_orders'), path('prepare-order//', self.admin_site.admin_view(self.prepare_order), name='prepare_order'), path('cancel-and-refund-order//', self.admin_site.admin_view(self.cancel_and_refund_order), name='cancel_and_refund_order'), ] return custom_urls + urls def prepare_all_orders(self, request): if request.method == 'POST': Order.objects.filter(status=OrderStatus.PAID).update(status=OrderStatus.PREPARED) self.message_user(request, "All orders have been marked as prepared.") return redirect('admin:shop_order_changelist') def prepare_order(self, request, order_id): if request.method == 'POST': order = Order.objects.get(id=order_id) order.status = OrderStatus.PREPARED order.save() self.message_user(request, f"Order #{order_id} has been marked as prepared.") return redirect('admin:shop_order_changelist') def cancel_and_refund_order(self, request, order_id): if request.method == 'POST': order = Order.objects.get(id=order_id) try: # Reuse the cancel_order logic from your views from .views import cancel_order cancel_order(request, order_id) self.message_user(request, f"Order #{order_id} has been cancelled and refunded.") except Exception as e: self.message_user(request, f"Error cancelling order: {str(e)}", level='ERROR') return redirect('admin:shop_order_changelist') def change_order_status(self, request, queryset): """Admin action to change the status of selected orders""" form = None if 'apply' in request.POST: form = ChangeOrderStatusForm(request.POST) if form.is_valid(): status = form.cleaned_data['status'] count = 0 for order in queryset: order.status = status order.save() count += 1 self.message_user(request, f"{count} orders have been updated to status '{OrderStatus(status).label}'.") return HttpResponseRedirect(request.get_full_path()) if not form: form = ChangeOrderStatusForm(initial={'_selected_action': request.POST.getlist('_selected_action')}) context = { 'title': 'Change Order Status', 'orders': queryset, 'form': form, 'action': 'change_order_status' } return render(request, 'admin/shop/order/change_status.html', context) change_order_status.short_description = "Change status for selected orders" class GuestUserOrderInline(admin.TabularInline): model = Order extra = 0 readonly_fields = ('date_ordered', 'total_price') can_delete = False show_change_link = True exclude = ('user',) # Exclude the user field from the inline display @admin.register(GuestUser) class GuestUserAdmin(admin.ModelAdmin): list_display = ('email', 'phone') inlines = [GuestUserOrderInline] @admin.register(Coupon) class CouponAdmin(admin.ModelAdmin): list_display = ('code', 'discount_amount', 'discount_percent', 'is_active', 'valid_from', 'valid_to', 'current_uses', 'max_uses') list_filter = ('is_active', 'valid_from', 'valid_to') search_fields = ('code', 'description') readonly_fields = ('current_uses', 'created_at', 'stripe_coupon_id') fieldsets = ( ('Basic Information', { 'fields': ('code', 'description', 'is_active') }), ('Discount', { 'fields': ('discount_amount', 'discount_percent') }), ('Validity', { 'fields': ('valid_from', 'valid_to', 'max_uses', 'current_uses') }), ('Stripe Information', { 'fields': ('stripe_coupon_id',), 'classes': ('collapse',) }), ) @admin.register(CouponUsage) class CouponUsageAdmin(admin.ModelAdmin): list_display = ('coupon', 'user', 'guest_email', 'order', 'used_at') list_filter = ('used_at',) search_fields = ('coupon__code', 'user__username', 'user__email', 'guest_email') readonly_fields = ('used_at',)