diff --git a/web/migrations/0064_alter_goods_options_alter_order_options.py b/web/migrations/0064_alter_goods_options_alter_order_options.py
new file mode 100644
index 000000000..e9d7cd91a
--- /dev/null
+++ b/web/migrations/0064_alter_goods_options_alter_order_options.py
@@ -0,0 +1,21 @@
+# Generated by Django 5.1.15 on 2026-02-23 05:38
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('web', '0063_virtualclassroom_virtualclassroomcustomization_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='goods',
+ options={'ordering': ['-created_at']},
+ ),
+ migrations.AlterModelOptions(
+ name='order',
+ options={'ordering': ['-created_at']},
+ ),
+ ]
diff --git a/web/models.py b/web/models.py
index 6b8ea8ef4..3a26948e3 100644
--- a/web/models.py
+++ b/web/models.py
@@ -1318,6 +1318,9 @@ def save(self, *args, **kwargs):
self.slug = slug
super().save(*args, **kwargs)
+ class Meta:
+ ordering = ["-created_at"]
+
def __str__(self):
return f"{self.name} (${self.price})"
@@ -1636,6 +1639,9 @@ def save(self, *args, **kwargs):
def generate_tracking_number(self):
return f"TRACK-{self.pk}-{int(time.time())}-{uuid.uuid4().hex[:6].upper()}"
+ class Meta:
+ ordering = ["-created_at"]
+
def __str__(self):
return f"Order #{self.id} ({self.user.email})"
diff --git a/web/templates/base.html b/web/templates/base.html
index 7afa700a9..cac546582 100644
--- a/web/templates/base.html
+++ b/web/templates/base.html
@@ -343,16 +343,22 @@
- {% if request.user.cart.item_count > 0 or request.session.session_key and request.session.session_key|get_cart_item_count > 0 %}
-
- {% if request.user.is_authenticated %}
- {{ request.user.cart.item_count }}
- {% else %}
- {{ request.session.session_key|get_cart_item_count }}
+ {% with item_count=request.user.cart.item_count|default:0 %}
+ {% with s_count=request.session.session_key|get_cart_item_count|default:0 %}
+ {% if item_count > 0 or s_count > 0 %}
+
+ {% if request.user.is_authenticated %}
+ {{ item_count }}
+ {% else %}
+ {{ s_count }}
+ {% endif %}
+
{% endif %}
-
- {% endif %}
+ {% endwith %}
+ {% endwith %}
@@ -758,15 +774,19 @@
Cart
- {% if request.user.cart.item_count > 0 or request.session.session_key and request.session.session_key|get_cart_item_count > 0 %}
-
- {% if request.user.is_authenticated %}
- {{ request.user.cart.item_count }}
- {% else %}
- {{ request.session.session_key|get_cart_item_count }}
+ {% with item_count=request.user.cart.item_count|default:0 %}
+ {% with s_count=request.session.session_key|get_cart_item_count|default:0 %}
+ {% if item_count > 0 or s_count > 0 %}
+
+ {% if request.user.is_authenticated %}
+ {{ item_count }}
+ {% else %}
+ {{ s_count }}
+ {% endif %}
+
{% endif %}
-
- {% endif %}
+ {% endwith %}
+ {% endwith %}
{% if user.is_authenticated %}
@@ -1060,7 +1080,8 @@ CONNECT WITH
About Us
Terms & Conditions
+ class="hover:text-teal-600 dark:hover:text-teal-400">Terms &
+ Conditions
Privacy Policy
CONNECT WITH
}
});
}
+
+ // Global Search Functionality
+ document.addEventListener('DOMContentLoaded', function() {
+ const searchInputs = document.querySelectorAll('.navbar-search-input');
+ let debounceTimer;
+
+ searchInputs.forEach(input => {
+ const resultsContainer = input.closest('.relative').querySelector('.navbar-search-results');
+
+ input.addEventListener('input', function() {
+ clearTimeout(debounceTimer);
+ const query = this.value.trim();
+
+ if (query.length < 2) {
+ resultsContainer.innerHTML = '';
+ resultsContainer.classList.add('hidden');
+ return;
+ }
+
+ debounceTimer = setTimeout(() => {
+ fetch(`/api/search/?q=${encodeURIComponent(query)}`)
+ .then(response => response.json())
+ .then(data => {
+ if (data.results.length > 0) {
+ resultsContainer.innerHTML = data.results.map(result => `
+
+
+
+
+
+
${result.title}
+
${result.type}
+
+
+ `).join('');
+ resultsContainer.classList.remove('hidden');
+ } else {
+ resultsContainer.innerHTML = `
+
+ No results found for "${query}"
+
+ `;
+ resultsContainer.classList.remove('hidden');
+ }
+ });
+ }, 300);
+ });
+
+ // Close results on escape
+ input.addEventListener('keydown', function(e) {
+ if (e.key === 'Escape') {
+ resultsContainer.classList.add('hidden');
+ }
+ });
+ });
+
+ // Close results when clicking outside
+ document.addEventListener('click', function(event) {
+ if (!event.target.closest('.relative')) {
+ document.querySelectorAll('.navbar-search-results').forEach(container => {
+ container.classList.add('hidden');
+ });
+ }
+ });
+ });
{% block extra_js %}
{% endblock extra_js %}
diff --git a/web/urls.py b/web/urls.py
index 3fb4e298a..8e511d00f 100644
--- a/web/urls.py
+++ b/web/urls.py
@@ -102,6 +102,7 @@
# Course Management
path("courses/create/", views.create_course, name="create_course"),
path("courses/search/", views.course_search, name="course_search"),
+ path("api/search/", views.global_search_api, name="global_search_api"),
path("courses//", views.course_detail, name="course_detail"),
path("courses//enroll/", views.enroll_course, name="enroll_course"),
path("courses//add-session/", views.add_session, name="add_session"),
diff --git a/web/views.py b/web/views.py
index b3a310687..25fc4433b 100644
--- a/web/views.py
+++ b/web/views.py
@@ -8839,3 +8839,38 @@ def leave_session_waiting_room(request, course_slug):
messages.info(request, "You are not in the session waiting room for this course.")
return redirect("course_detail", slug=course_slug)
+
+
+def global_search_api(request):
+ """API endpoint for global search of subjects and courses."""
+ query = request.GET.get("q", "").strip()
+ if not query or len(query) < 2:
+ return JsonResponse({"results": []})
+
+ results = []
+
+ # Search Subjects
+ subjects = Subject.objects.filter(name__icontains=query)[:5]
+ for subject in subjects:
+ results.append(
+ {
+ "type": "subject",
+ "title": subject.name,
+ "url": reverse("course_search") + f"?subject={subject.slug}",
+ "icon": "fas fa-tag",
+ }
+ )
+
+ # Search Courses
+ courses = Course.objects.filter(Q(title__icontains=query) | Q(tags__icontains=query), status="published")[:5]
+ for course in courses:
+ results.append(
+ {
+ "type": "course",
+ "title": course.title,
+ "url": reverse("course_detail", kwargs={"slug": course.slug}),
+ "icon": "fas fa-book",
+ }
+ )
+
+ return JsonResponse({"results": results})