diff --git a/BookStation/BookStation/settings.py b/BookStation/BookStation/settings.py index 9ce79613..7b2ce2e7 100644 --- a/BookStation/BookStation/settings.py +++ b/BookStation/BookStation/settings.py @@ -36,7 +36,9 @@ 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', - 'django.contrib.staticfiles', 'django.contrib.humanize', + 'django.contrib.staticfiles', + 'widget_tweaks', + 'django.contrib.humanize', 'accounts', 'books', 'orders', diff --git a/BookStation/books/forms.py b/BookStation/books/forms.py index 1c8ada93..7f39ca1d 100644 --- a/BookStation/books/forms.py +++ b/BookStation/books/forms.py @@ -35,4 +35,5 @@ class Meta: class CategoryForm(forms.ModelForm): class Meta: model = Category - fields = ['name', 'description'] \ No newline at end of file + fields = ['name', 'description'] + diff --git a/BookStation/books/migrations/0002_alter_author_birth_date.py b/BookStation/books/migrations/0002_alter_author_birth_date.py new file mode 100644 index 00000000..9b2eb21a --- /dev/null +++ b/BookStation/books/migrations/0002_alter_author_birth_date.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.1 on 2025-06-10 17:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("books", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="author", + name="birth_date", + field=models.DateField(blank=True, null=True), + ), + ] diff --git a/BookStation/books/models.py b/BookStation/books/models.py index 229c888d..63fe239c 100644 --- a/BookStation/books/models.py +++ b/BookStation/books/models.py @@ -30,7 +30,7 @@ def __str__(self): class Book(models.Model): title = models.CharField(max_length=200) - price = models.DecimalField(max_digits=6, decimal_places=2) + price = models.DecimalField(max_digits=8, decimal_places=4) description = models.TextField(blank=True) stock = models.PositiveIntegerField(default=0) #tồn kho cover_image = models.ImageField(upload_to='book_covers/', blank=True, null=True) diff --git a/BookStation/books/templates/authors/author_confirm_delete.html b/BookStation/books/templates/authors/author_confirm_delete.html index 58d5ba32..64e96309 100644 --- a/BookStation/books/templates/authors/author_confirm_delete.html +++ b/BookStation/books/templates/authors/author_confirm_delete.html @@ -14,7 +14,6 @@ -
diff --git a/BookStation/orders/models.py b/BookStation/orders/models.py index 4b8f9f95..7cd64df1 100644 --- a/BookStation/orders/models.py +++ b/BookStation/orders/models.py @@ -1,6 +1,7 @@ from django.db import models from django.conf import settings from books.models import Book +from decimal import Decimal class Order(models.Model): STATUS_CHOICES = [ @@ -41,4 +42,6 @@ def __str__(self): return f"{self.quantity} x {self.book.title}" def subtotal(self): + if self.price is None: + return Decimal('0.00') # Trả về 0 nếu price là None return self.quantity * self.price diff --git a/BookStation/staff/forms.py b/BookStation/staff/forms.py index 3f2bdd8e..ca95e755 100644 --- a/BookStation/staff/forms.py +++ b/BookStation/staff/forms.py @@ -2,5 +2,31 @@ from accounts.models import Users from django import forms from books.models import Book +from django import forms +from orders.forms import OrderForm +from orders.models import Order +from orders.forms import OrderItemFormSet as BaseOrderItemFormSet + + +class BookImportForm(forms.Form): + json_file = forms.FileField(label="File JSON chứa thông tin sách", required=True) + image_zip = forms.FileField(label="File ZIP chứa ảnh bìa sách", required=True) + +class StaffOrderForm(OrderForm): + class Meta(OrderForm.Meta): + fields = ['status', 'note'] # Thêm status + widgets = { + 'status': forms.Select(attrs={'class': 'w-full p-2 border rounded'}), + 'note': forms.Textarea(attrs={'rows': 3, 'class': 'w-full p-2 border rounded'}), + } +class StaffOrderItemFormSet(BaseOrderItemFormSet): + def clean(self): + super().clean() + for form in self.forms: + if not form.cleaned_data.get('DELETE'): + book = form.cleaned_data.get('book') + quantity = form.cleaned_data.get('quantity') + if book and quantity and quantity > book.stock: + raise forms.ValidationError(f"Sách {book.title} chỉ còn {book.stock} cuốn.") diff --git a/BookStation/staff/templates/staff/accounts/add_users.html b/BookStation/staff/templates/staff/accounts/add_users.html index 2a94d623..0576fd27 100644 --- a/BookStation/staff/templates/staff/accounts/add_users.html +++ b/BookStation/staff/templates/staff/accounts/add_users.html @@ -1,127 +1,79 @@ +{% extends 'staff/dashboard_staff.html' %} {% load static %} - - - - - - Thêm người dùng - BookStation - - - - - -
-
+{% load widget_tweaks %} {# <-- Thêm dòng này #} -
-
- - Quản trị nhân viên - -

Thêm người dùng mới

-

Tạo tài khoản mới cho nhân viên hoặc khách hàng

-
- - {% if messages %} -
- {% for message in messages %} -
- - {{ message }} -
- {% endfor %} -
- {% endif %} - -
- {% csrf_token %} - -
-
- - - {% if form.first_name.errors %} -
- {{ form.first_name.errors|striptags }} -
- {% endif %} -
- -
- - - {% if form.last_name.errors %} -
- {{ form.last_name.errors|striptags }} -
- {% endif %} -
- -
- - - {% if form.username.errors %} -
- {{ form.username.errors|striptags }} -
- {% endif %} -
+{% block content %} +
+

+ ➕ Thêm người dùng mới +

-
- - - {% if form.email.errors %} -
- {{ form.email.errors|striptags }} -
- {% endif %} + {# Hiển thị thông báo (từ Django messages framework) #} + {% if messages %} +
+ {% for message in messages %} +
+ {{ message }}
+ {% endfor %} +
+ {% endif %} -
- - + + {% csrf_token %} - {% if form.phone.errors %} -
- {{ form.phone.errors|striptags }} -
- {% endif %} -
+ {# Hiển thị lỗi tổng quát của form (non-field errors) #} + {% if form.non_field_errors %} + + {% endif %} -
- - - {% if form.password1.errors %} -
- {{ form.password1.errors|striptags }} -
+ {# Lặp qua từng trường trong form để hiển thị #} + {% for field in form %} +
+
+ + {# Sử dụng |add_class để thêm Tailwind classes #} + {# Thêm placeholder và icon nếu bạn muốn, sử dụng |attr:"placeholder:Giá trị" #} + {% render_field field class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" %} -
- - - {% if form.password2.errors %} -
- {{ form.password2.errors|striptags }} -
- {% endif %} -
+ {% if field.help_text %} +

{{ field.help_text }}

+ {% endif %} + {% if field.errors %} +
+ {% for error in field.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %}
+ {% endfor %} - - - -
-
- - -
+
- - +{% endblock content %} \ No newline at end of file diff --git a/BookStation/staff/templates/staff/accounts/change_user.html b/BookStation/staff/templates/staff/accounts/change_user.html index 7dcfc577..471d4835 100644 --- a/BookStation/staff/templates/staff/accounts/change_user.html +++ b/BookStation/staff/templates/staff/accounts/change_user.html @@ -1,26 +1,141 @@ - - - - - Chỉnh sửa người dùng - - -

Chỉnh sửa người dùng: {{ user.username }}

- -

Thông tin cá nhân

-
- {% csrf_token %} - {{ form.as_p }} - -
- -

Đổi mật khẩu

-
- {% csrf_token %} - {{ password_form.as_p }} - -
- -

⬅ Quay lại danh sách người dùng

- - +{% extends 'staff/dashboard_staff.html' %} +{% load static %} +{% load widget_tweaks %} + +{% block content %} +
+

+ Chỉnh sửa người dùng: {{ user.username }} +

+ + {# Hiển thị thông báo (từ Django messages framework) #} + {% if messages %} +
+ {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} +
+ {% endif %} + + {# Form chỉnh sửa thông tin cá nhân #} +
+

+ Thông tin cá nhân +

+
+ {% csrf_token %} + {# Thêm một trường ẩn để xác định form nào đang được submit #} + + + {# Hiển thị lỗi tổng quát của form #} + {% if form.non_field_errors %} + + {% endif %} + + {% for field in form %} +
+ + {% render_field field class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" %} + + {% if field.help_text %} +

{{ field.help_text }}

+ {% endif %} + {% if field.errors %} +
+ {% for error in field.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ {% endfor %} + +
+ +
+
+
+ + {# Form đổi mật khẩu #} +
+

+ Đổi mật khẩu +

+
+ {% csrf_token %} + {# Thêm một trường ẩn để xác định form nào đang được submit #} + + + {# Hiển thị lỗi tổng quát của form #} + {% if password_form.non_field_errors %} + + {% endif %} + + {% for field in password_form %} +
+ + {% render_field field class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" %} + + {% if field.help_text %} +

{{ field.help_text }}

+ {% endif %} + {% if field.errors %} +
+ {% for error in field.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ {% endfor %} + +
+ +
+
+
+ + +
+{% endblock content %} \ No newline at end of file diff --git a/BookStation/staff/templates/staff/accounts/list_users.html b/BookStation/staff/templates/staff/accounts/list_users.html index d81c189a..805ca83c 100644 --- a/BookStation/staff/templates/staff/accounts/list_users.html +++ b/BookStation/staff/templates/staff/accounts/list_users.html @@ -1,47 +1,106 @@ - - - - Danh sách người dùng - - -

📋 Danh sách người dùng

+{% extends 'staff/dashboard_staff.html' %} +{% load static %} - -
- - -
+{% block content %} +
+

+ 📋 Danh sách người dùng +

-
+ {# Đã sửa action của form để trỏ đúng URL #} +
+
+ {# Giữ nguyên input HTML gốc của bạn. 'name="q"' là quan trọng để tìm kiếm hoạt động. #} + +
+ +
+
+ +
- - - - - - - - - {% for u in users %} - - - - - - - - {% empty %} - - {% endfor %} -
IDUsernameHọ tênEmailThao tác
{{ u.id }}{{ u.username }}{{ u.first_name }} {{ u.last_name }}{{ u.email }} - {% if can_change_user %} - ✏️ Sửa +
+ + + + + + + + + + + + {% for u in users %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
IDUsernameHọ tênEmailThao tác
{{ u.id }}{{ u.username }}{{ u.first_name }} {{ u.last_name }}{{ u.email }} + {% if can_change_user %} {# Giữ nguyên biến 'can_change_user' như trong code gốc của bạn #} + + ✏️ Sửa + + {% else %} + + {% endif %} +
+ Không tìm thấy người dùng nào. +
+
+ + {# Pagination (phân trang) #} + {# ĐÃ BỎ ĐIỀU KIỆN 'if users.has_other_pages' ĐỂ LUÔN HIỆN THÔNG TIN TRANG #} +
+
+ {% if users.has_previous %} + + Trước + + {% else %} + + Trước + + {% endif %} +
+ +
+ Trang {{ users.number }} trên {{ users.paginator.num_pages }} +
+ +
+ {% if users.has_next %} + + Tiếp + {% else %} - ❌ + + Tiếp + {% endif %} -
Không tìm thấy người dùng nào.
+
+
+ {# Kết thúc phần phân trang #} -
- ⬅️ Về dashboard - - +{# #} +{# ⬅️ Về dashboard#} +{# #} +
+{% endblock content %} \ No newline at end of file diff --git a/BookStation/staff/templates/staff/books/add_book.html b/BookStation/staff/templates/staff/books/add_book.html index 57c29085..719d057a 100644 --- a/BookStation/staff/templates/staff/books/add_book.html +++ b/BookStation/staff/templates/staff/books/add_book.html @@ -1,70 +1,297 @@ +{#{% extends 'staff/dashboard_staff.html' %} {# Giả định bạn có một base template chung#} +{#{% load static %}#} +{#{% load widget_tweaks %} {# Vẫn cần nếu các trường khác dùng render_field#} +{##} +{#{% block content %}#} +{#
#} +{#

#} +{# Thêm sách mới#} +{#

#} +{##} +{#
#} +{# {% csrf_token %}#} +{# {% for field in form %}#} +{#
#} +{# #} +{##} +{# ĐIỀU CHỈNH LẠI DÒNG NÀY: Xử lý riêng trường 'categories' bằng cách render thủ công từng checkbox#} +{# {% if field.name == 'categories' %}#} +{#
#} +{# {% for choice in field %}#} +{#
#} +{# RENDER THỦ CÔNG THẺ INPUT CHECKBOX VÀ GÁN CLASS TRỰC TIẾP#} +{# #} +{# #} +{#
#} +{# {% endfor %}#} +{#
#} +{# {% elif field.field.widget.input_type == 'checkbox' %} {# Xử lý các checkbox thông thường khác (không phải categories)#} +{#
#} +{# {% render_field field class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" %}#} +{# #} +{#
#} +{# {% else %} {# Xử lý các trường input/textarea/select thông thường#} +{# {% render_field field class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" %}#} +{# {% endif %}#} +{##} +{# {% if field.help_text %}#} +{#

{{ field.help_text }}

#} +{# {% endif %}#} +{##} +{# {% if field.errors %}#} +{#
#} +{# {% for error in field.errors %}#} +{#

{{ error }}

#} +{# {% endfor %}#} +{#
#} +{# {% endif %}#} +{#
#} +{# {% endfor %}#} +{##} +{#
#} +{# #} +{# Hủy#} +{# #} +{# #} +{#
#} +{# #} +{# {% csrf_token %}#} +{#
#} +{# #} +{# #} +{#
#} +{#
#} +{# #} +{# #} +{#
#} +{#
#} +{# #} +{#
#} +{#
#} +{# #} +{#
#} +{#{% endblock content %}#} +{#{% extends 'staff/dashboard_staff.html' %}#} +{#{% load static %}#} +{#{% load widget_tweaks %}#} +{##} +{#{% block content %}#} +{#
#} +{#

#} +{# Thêm sách mới#} +{#

#} +{##} + {# FORM THÊM SÁCH THỦ CÔNG #} +{#
#} +{# {% csrf_token %}#} +{# {% for field in form %}#} +{#
#} +{# #} +{##} +{# {% if field.name == 'categories' %}#} +{#
#} +{# {% for choice in field %}#} +{#
#} +{# #} +{# #} +{#
#} +{# {% endfor %}#} +{#
#} +{# {% elif field.field.widget.input_type == 'checkbox' %}#} +{#
#} +{# {% render_field field class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" %}#} +{# #} +{#
#} +{# {% else %}#} +{# {% render_field field class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" %}#} +{# {% endif %}#} +{##} +{# {% if field.help_text %}#} +{#

{{ field.help_text }}

#} +{# {% endif %}#} +{##} +{# {% if field.errors %}#} +{#
#} +{# {% for error in field.errors %}#} +{#

{{ error }}

#} +{# {% endfor %}#} +{#
#} +{# {% endif %}#} +{#
#} +{# {% endfor %}#} +{##} +{#
#} +{# #} +{# Hủy#} +{# #} +{# #} +{#
#} +{#
#} +{##} +{#
#} +{##} + {# FORM NHẬP SÁCH TỪ TỆP ZIP #} +{#
#} +{# {% csrf_token %}#} +{#
#} +{# #} +{# #} +{#
#} +{#
#} +{# #} +{#
#} +{#
#} +{#
#} +{#{% endblock content %}#} +{% extends 'staff/dashboard_staff.html' %} {% load static %} +{% load widget_tweaks %} - - - - - Thêm sách mới - BookStation - - - -
- +{% block content %} +
+

+ Thêm sách mới +

-
-
-
-
-

📘 Thêm sách mới

+ {# FORM 1: THÊM SÁCH THỦ CÔNG #} +
+ {% csrf_token %} + {% for field in form %} +
+ - - {% csrf_token %} - {% for field in form %} -
- - - {{ field }} - - {% if field.help_text %} -
{{ field.help_text }}
- {% endif %} - - {% if field.errors %} -
- {% for error in field.errors %} - {{ error }} - {% endfor %} + {% if field.name == 'categories' %} +
+ {% for choice in field %} +
+ +
- {% endif %} -
{% endfor %} +
+ {% elif field.field.widget.input_type == 'checkbox' %} +
+ {% render_field field class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" %} + +
+ {% else %} + {% render_field field class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" %} + {% endif %} + + {% if field.help_text %} +

{{ field.help_text }}

+ {% endif %} -
- - - Hủy - -
- -
+ {% if field.errors %} +
+ {% for error in field.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %}
+ {% endfor %} + +
+ + Hủy + +
-
-
+ + +
- - - - +

Hoặc nhập sách từ file

+ + +
+ {% csrf_token %} +
+ + +
+
+ +
+
+ + {# FORM 3: NHẬP TỪ FILE ZIP ẢNH #} +
+ {% csrf_token %} +
+ + +
+
+ +
+
+
+{% endblock content %} +x \ No newline at end of file diff --git a/BookStation/staff/templates/staff/books/change_book.html b/BookStation/staff/templates/staff/books/change_book.html index ec42173e..ed9c3c90 100644 --- a/BookStation/staff/templates/staff/books/change_book.html +++ b/BookStation/staff/templates/staff/books/change_book.html @@ -1,91 +1,78 @@ +{% extends 'staff/dashboard_staff.html' %} {% load static %} {% block title %} - Sửa sách - {{ book.title }} - BookStation + Sửa Sách - {{ book.title }} - BookStation {% endblock %} {% block content %} -
-