aboutsummaryrefslogtreecommitdiff
path: root/store/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'store/views.py')
-rw-r--r--store/views.py433
1 files changed, 433 insertions, 0 deletions
diff --git a/store/views.py b/store/views.py
new file mode 100644
index 0000000..e77cd29
--- /dev/null
+++ b/store/views.py
@@ -0,0 +1,433 @@
+from django.shortcuts import render, redirect, get_object_or_404
+from django.contrib.auth import login, authenticate, logout
+from django.contrib.auth.forms import AuthenticationForm
+from django.contrib.auth.decorators import login_required
+from django.contrib import messages
+from django.contrib.auth.decorators import user_passes_test
+from django.core.paginator import Paginator
+from django.db.models import Q
+from django.db import transaction
+from .forms import UserRegisterForm, BookForm, ImportBookForm
+from .models import Book, Order, OrderItem, User, Comment
+
+import json
+import threading
+import os
+import tempfile
+
+# Global dictionary to track import status: {user_id: {'status': 'running'|'done', 'count': 0}}
+import_status = {}
+
+def process_import_thread(user_id, file_path):
+ global import_status
+ count = 0
+ try:
+ with open(file_path, 'r', encoding='utf-8') as f:
+ for line in f:
+ try:
+ data = json.loads(line)
+ metadata = data.get('metadata')
+ if not metadata:
+ continue
+
+ isbn = metadata.get('isbn')
+ if not isbn:
+ continue
+
+ defaults = {
+ 'title': metadata.get('title', 'Unknown Title'),
+ 'author': metadata.get('author', 'Unknown Author'),
+ 'publisher': metadata.get('publisher', 'Unknown Publisher'),
+ 'price': int(metadata.get('price', 50) or 50),
+ 'stock': 10,
+ 'description': metadata.get('description', ''),
+ 'cover': metadata.get('cover', '')
+ }
+
+ Book.objects.update_or_create(
+ isbn=isbn,
+ defaults=defaults
+ )
+ count += 1
+ import_status[user_id]['count'] = count
+ except Exception as e:
+ print(f"Error importing line: {e}")
+ continue
+ except Exception as e:
+ print(f"File processing error: {e}")
+ finally:
+ import_status[user_id]['status'] = 'done'
+ if os.path.exists(file_path):
+ os.remove(file_path)
+
+def is_staff(user):
+ return user.is_staff or user.is_superuser or (user.roles and 'admin' in user.roles)
+
+@user_passes_test(is_staff)
+def import_books(request):
+ global import_status
+ user_id = request.user.id
+
+ if request.method == 'POST':
+ # Block new import if one is already running
+ if user_id in import_status and import_status[user_id]['status'] == 'running':
+ count = import_status[user_id]['count']
+ messages.warning(request, f"Import already in progress. {count} books imported so far.")
+ return redirect('import_books')
+
+ form = ImportBookForm(request.POST, request.FILES)
+ if form.is_valid():
+ uploaded_file = request.FILES['file']
+
+ # Save to temporary file to read in thread
+ fd, path = tempfile.mkstemp()
+ try:
+ with os.fdopen(fd, 'wb') as tmp:
+ for chunk in uploaded_file.chunks():
+ tmp.write(chunk)
+ except Exception as e:
+ os.remove(path)
+ messages.error(request, f"Error saving file: {e}")
+ return redirect('import_books')
+
+ # Initialize status
+ import_status[user_id] = {'status': 'running', 'count': 0}
+
+ # Start thread
+ thread = threading.Thread(target=process_import_thread, args=(user_id, path))
+ thread.daemon = True
+ thread.start()
+
+ messages.success(request, "Import started in the background. You can continue using the site.")
+ return redirect('book_list')
+ else:
+ form = ImportBookForm()
+ if user_id in import_status and import_status[user_id]['status'] == 'running':
+ messages.info(request, f"An import is currently running. {import_status[user_id]['count']} books imported so far.")
+
+ return render(request, 'store/import_books.html', {'form': form, 'title': 'Import Books'})
+
+@user_passes_test(is_staff)
+def add_book(request):
+ if request.method == 'POST':
+ form = BookForm(request.POST, request.FILES)
+ if form.is_valid():
+ form.save()
+ messages.success(request, "Book added successfully.")
+ return redirect('book_list')
+ else:
+ form = BookForm()
+ return render(request, 'store/add_edit_book.html', {'form': form, 'title': 'Add Book'})
+
+@user_passes_test(is_staff)
+def edit_book(request, pk):
+ book = get_object_or_404(Book, pk=pk)
+ if request.method == 'POST':
+ form = BookForm(request.POST, request.FILES, instance=book)
+ if form.is_valid():
+ form.save()
+ messages.success(request, "Book updated successfully.")
+ return redirect('book_detail', pk=book.pk)
+ else:
+ form = BookForm(instance=book)
+ return render(request, 'store/add_edit_book.html', {'form': form, 'book': book, 'title': 'Edit Book'})
+
+@user_passes_test(is_staff)
+def delete_book(request, pk):
+ book = get_object_or_404(Book, pk=pk)
+ if request.method == 'POST':
+ book.delete()
+ messages.success(request, "Book deleted successfully.")
+ return redirect('book_list')
+ return render(request, 'store/delete_book_confirm.html', {'book': book})
+
+
+@user_passes_test(is_staff)
+def order_management(request):
+ orders = Order.objects.all().order_by('-createdAt')
+ return render(request, 'store/order_management.html', {'orders': orders})
+
+@user_passes_test(is_staff)
+def update_order_status(request, pk):
+ order = get_object_or_404(Order, pk=pk)
+ if request.method == 'POST':
+ new_status = request.POST.get('status')
+ if new_status in ['2', '3'] and str(order.status) != new_status: # 2 for shipped, 3 for rejected
+ try:
+ with transaction.atomic():
+ if new_status == '2': # Shipped
+ if order.status == 1: # Only ship if pending
+ for item in order.items.all():
+ book = Book.objects.select_for_update().get(pk=item.book.pk)
+ # Stock was already reduced at checkout, no need to reduce again
+ order.status = 2 # Mark as shipped
+ messages.success(request, f"Order #{order.id} marked as shipped.")
+ else:
+ messages.error(request, f"Order #{order.id} cannot be shipped from its current status.")
+
+ elif new_status == '3': # Rejected
+ if order.status == 1: # Only reject if pending
+ for item in order.items.all():
+ book = Book.objects.select_for_update().get(pk=item.book.pk)
+ book.stock += item.amount # Add stock back
+ book.save()
+ order.status = 3 # Mark as rejected
+ messages.success(request, f"Order #{order.id} marked as rejected and stock restored.")
+ else:
+ messages.error(request, f"Order #{order.id} cannot be rejected from its current status.")
+ order.save()
+
+ except Exception as e:
+ messages.error(request, f"Error updating order status: {e}")
+ else:
+ messages.error(request, "Invalid status update.")
+ return redirect('order_management')
+
+def book_list(request):
+ query = request.GET.get('q')
+ search_type = request.GET.get('type', 'title')
+
+ if query:
+ if search_type == 'author':
+ books_queryset = Book.objects.filter(author__icontains=query)
+ elif search_type == 'publisher':
+ books_queryset = Book.objects.filter(publisher__icontains=query)
+ elif search_type == 'isbn':
+ books_queryset = Book.objects.filter(isbn__icontains=query)
+ else: # Default to title search
+ books_queryset = Book.objects.filter(title__icontains=query)
+ else:
+ books_queryset = Book.objects.all()
+
+ paginator = Paginator(books_queryset, 12)
+ page_number = request.GET.get('page')
+ page_obj = paginator.get_page(page_number)
+
+ return render(request, 'store/book_list.html', {
+ 'books': page_obj,
+ 'query': query,
+ 'search_type': search_type
+ })
+
+def book_detail(request, pk):
+ book = get_object_or_404(Book, pk=pk)
+ comments = Comment.objects.filter(book=book).order_by('-createdAt')
+ return render(request, 'store/book_detail.html', {'book': book, 'comments': comments})
+
+@login_required
+def add_to_cart(request, pk):
+ book = get_object_or_404(Book, pk=pk)
+ if request.method == 'POST':
+ try:
+ quantity_to_add = int(request.POST.get('quantity', '1'))
+ if quantity_to_add <= 0:
+ raise ValueError
+ except (ValueError, TypeError):
+ messages.error(request, "Invalid quantity.")
+ return redirect(request.META.get('HTTP_REFERER', 'book_list'))
+
+ cart = request.session.get('cart', {})
+ current_quantity = cart.get(str(book.id), 0)
+
+ new_quantity = current_quantity + quantity_to_add
+
+ if book.stock < new_quantity:
+ messages.error(request, f"Sorry, only {book.stock} copies of '{book.title}' are available.")
+ return redirect(request.META.get('HTTP_REFERER', 'book_list'))
+
+ cart[str(book.id)] = new_quantity
+ request.session['cart'] = cart
+ request.session.modified = True
+ messages.success(request, f"{quantity_to_add} {'copy' if quantity_to_add == 1 else 'copies'} of '{book.title}' added to your cart.")
+ return redirect('cart_detail')
+
+ return redirect('book_list')
+
+@login_required
+def view_cart(request):
+ session_cart = request.session.get('cart', {})
+ cart_items_data = []
+ total_price = 0
+
+ for book_id, quantity in session_cart.items():
+ book = get_object_or_404(Book, pk=book_id)
+ item_total = book.price * quantity
+ total_price += item_total
+ cart_items_data.append({
+ 'book': book,
+ 'quantity': quantity,
+ 'total_price': item_total,
+ 'id': book_id # For removing/updating
+ })
+
+ return render(request, 'store/cart.html', {'cart_items': cart_items_data, 'total_price': total_price})
+
+@login_required
+def remove_from_cart(request, pk):
+ cart = request.session.get('cart', {})
+ book_id_str = str(pk)
+ if book_id_str in cart:
+ del cart[book_id_str]
+ request.session['cart'] = cart
+ request.session.modified = True
+ messages.success(request, "Item removed from cart.")
+ return redirect('cart_detail')
+
+@login_required
+def update_cart_quantity(request, pk):
+ cart = request.session.get('cart', {})
+ book_id_str = str(pk)
+
+ if book_id_str not in cart:
+ messages.error(request, "Item not found in cart.")
+ return redirect('cart_detail')
+
+ book = get_object_or_404(Book, pk=pk)
+
+ if request.method == 'POST':
+ try:
+ quantity = int(request.POST.get('quantity', 1))
+ except (ValueError, TypeError):
+ messages.error(request, "Invalid quantity.")
+ return redirect('cart_detail')
+
+ if quantity > 0:
+ if book.stock < quantity:
+ messages.error(request, f"Sorry, only {book.stock} copies of '{book.title}' are available.")
+ return redirect('cart_detail')
+ cart[book_id_str] = quantity
+ messages.success(request, "Cart updated.")
+ else:
+ del cart[book_id_str]
+ messages.success(request, "Item removed from cart.")
+
+ request.session['cart'] = cart
+ request.session.modified = True
+
+ return redirect('cart_detail')
+
+@login_required
+def checkout(request):
+ session_cart = request.session.get('cart', {})
+ if not session_cart:
+ messages.warning(request, "Your cart is empty.")
+ return redirect('book_list')
+
+ cart_items_data = []
+ total_price = 0
+ for book_id, quantity in session_cart.items():
+ book = get_object_or_404(Book, pk=book_id)
+ item_total = book.price * quantity
+ total_price += item_total
+ cart_items_data.append({
+ 'book': book,
+ 'quantity': quantity,
+ 'total_price': item_total,
+ 'id': book_id
+ })
+
+ if request.method == 'POST':
+ address = request.POST.get('address')
+ if not address:
+ messages.error(request, "Please provide a shipping address.")
+ return render(request, 'store/checkout.html', {'cart_items': cart_items_data, 'total_price': total_price})
+
+ try:
+ with transaction.atomic():
+ # First, verify stock for all items in the current session cart
+ for item_data in cart_items_data:
+ book = Book.objects.select_for_update().get(pk=item_data['book'].pk)
+ if book.stock < item_data['quantity']:
+ raise ValueError(f"Insufficient stock for '{book.title}'. Only {book.stock} left.")
+
+ order = Order.objects.create(buyer=request.user, address=address)
+
+ for item_data in cart_items_data:
+ book = Book.objects.get(pk=item_data['book'].pk)
+ book.stock -= item_data['quantity']
+ book.save()
+
+ OrderItem.objects.create(
+ order=order,
+ book=book,
+ amount=item_data['quantity'],
+ bookPrice=book.price
+ )
+
+ request.session['cart'] = {} # Clear the cart
+ request.session.modified = True
+ messages.success(request, f"Order #{order.id} placed successfully!")
+ return redirect('order_list')
+
+ except ValueError as e:
+ messages.error(request, str(e))
+ return redirect('cart_detail')
+
+ return render(request, 'store/checkout.html', {'cart_items': cart_items_data, 'total_price': total_price})
+
+@login_required
+def order_list(request):
+ orders = Order.objects.filter(buyer=request.user).order_by('-createdAt')
+ return render(request, 'store/order_list.html', {'orders': orders})
+
+@login_required
+def post_comment(request, pk):
+ book = get_object_or_404(Book, pk=pk)
+ if request.method == 'POST':
+ content = request.POST.get('content')
+ if content:
+ Comment.objects.create(user=request.user, book=book, content=content)
+ messages.success(request, "Comment added.")
+ else:
+ messages.error(request, "Comment cannot be empty.")
+ return redirect('book_detail', pk=pk)
+
+@login_required
+def profile_view(request):
+ if request.method == 'POST':
+ user = request.user
+ user.name = request.POST.get('name')
+ user.phone = request.POST.get('phone')
+ user.address = request.POST.get('address')
+ if 'avatar' in request.FILES:
+ user.avatar = request.FILES['avatar']
+ user.save()
+ messages.success(request, "Profile updated.")
+ return redirect('profile')
+ return render(request, 'store/profile.html')
+
+def register_view(request):
+ if request.method == 'POST':
+ form = UserRegisterForm(request.POST)
+ if form.is_valid():
+ user = form.save()
+ login(request, user)
+ messages.success(request, "Registration successful.")
+ return redirect('book_list')
+ messages.error(request, "Unsuccessful registration. Invalid information.")
+ else:
+ form = UserRegisterForm()
+ return render(request, 'store/register.html', {'form': form})
+
+def login_view(request):
+ if request.method == "POST":
+ form = AuthenticationForm(request, data=request.POST)
+ if form.is_valid():
+ username = form.cleaned_data.get('username')
+ password = form.cleaned_data.get('password')
+ user = authenticate(username=username, password=password)
+ if user is not None:
+ login(request, user)
+ messages.info(request, f"You are now logged in as {username}.")
+ return redirect("book_list")
+ else:
+ messages.error(request, "Invalid username or password.")
+ else:
+ messages.error(request, "Invalid username or password.")
+ form = AuthenticationForm()
+ return render(request, 'store/login.html', {"form": form})
+
+def logout_view(request):
+ logout(request)
+ messages.info(request, "You have successfully logged out.")
+ return redirect("book_list")