diff --git a/common/static/common/scripts/app.js b/common/static/common/scripts/app.js
index cca3bb5d..e54772e0 100644
--- a/common/static/common/scripts/app.js
+++ b/common/static/common/scripts/app.js
@@ -133,7 +133,6 @@
});
}
-
init();
}
diff --git a/common/static/common/styles/_navigation.sass b/common/static/common/styles/_navigation.sass
index 4fca30aa..5ff4442b 100644
--- a/common/static/common/styles/_navigation.sass
+++ b/common/static/common/styles/_navigation.sass
@@ -10,6 +10,14 @@
display: flex
+padding(2)
+ .badge
+ +margin(3, left)
+ pointer-events: none
+
+.dropdown-item,
+.dropdown-toggle
+ white-space: nowrap
+
+media-md
.dropdown-menu-filter
gap: var(--spacer-1)
@@ -26,6 +34,7 @@
.dropdown-menu-filter-sort
max-height: calc(var(--spacer) * 24.25)
+ // TODO: decouple dropdown-menu-filter-sort with overflow auto from js dropdown-menu, so that caret symbol is shown when dropdown menu is active
overflow: auto
.dropdown-item
diff --git a/extensions/migrations/0032_extension_extensions__is_list_765936_idx_and_more.py b/extensions/migrations/0032_extension_extensions__is_list_765936_idx_and_more.py
new file mode 100644
index 00000000..eaf5b6ad
--- /dev/null
+++ b/extensions/migrations/0032_extension_extensions__is_list_765936_idx_and_more.py
@@ -0,0 +1,29 @@
+# Generated by Django 4.2.11 on 2024-05-31 10:12
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('extensions', '0031_extension_latest_version'),
+ ]
+
+ operations = [
+ migrations.AddIndex(
+ model_name='extension',
+ index=models.Index(fields=['is_listed', 'type', 'average_score'], name='extensions__is_list_765936_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='extension',
+ index=models.Index(fields=['is_listed', 'type', 'date_approved'], name='extensions__is_list_63d6cb_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='extension',
+ index=models.Index(fields=['is_listed', 'type', 'download_count'], name='extensions__is_list_46cfa1_idx'),
+ ),
+ migrations.AddIndex(
+ model_name='extension',
+ index=models.Index(fields=['is_listed', 'type', 'name'], name='extensions__is_list_7571c8_idx'),
+ ),
+ ]
diff --git a/extensions/models.py b/extensions/models.py
index da823ef6..8e2b7968 100644
--- a/extensions/models.py
+++ b/extensions/models.py
@@ -220,6 +220,12 @@ class Extension(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, models.Mod
objects = ExtensionManager()
class Meta:
+ indexes = [
+ models.Index(fields=['is_listed', 'type', 'average_score']),
+ models.Index(fields=['is_listed', 'type', 'date_approved']),
+ models.Index(fields=['is_listed', 'type', 'download_count']),
+ models.Index(fields=['is_listed', 'type', 'name']),
+ ]
ordering = ['-average_score', '-date_created', 'name']
def __str__(self):
diff --git a/extensions/templates/extensions/list.html b/extensions/templates/extensions/list.html
index 54016139..afbdf3df 100644
--- a/extensions/templates/extensions/list.html
+++ b/extensions/templates/extensions/list.html
@@ -1,5 +1,5 @@
{% extends "common/base.html" %}
-{% load i18n %}
+{% load common i18n %}
{% block page_title %}{% include "extensions/components/listing_title.html" %}{% endblock page_title %}
@@ -21,26 +21,15 @@
{% endif %}
- {% if tags %}
-
+
+
Filter
+ {% if tags %}
-
{% else %}
diff --git a/extensions/views/public.py b/extensions/views/public.py
index 22e5abd2..7f5c2955 100644
--- a/extensions/views/public.py
+++ b/extensions/views/public.py
@@ -1,8 +1,10 @@
+from collections import OrderedDict
import logging
from django.contrib.auth import get_user_model
-from django.db.models import Q
+from django.db.models import Count, Q
from django.shortcuts import get_object_or_404, redirect
+from django.utils.translation import gettext_lazy as _
from django.views.generic.list import ListView
@@ -44,6 +46,7 @@ class HomeView(ListedExtensionsView):
'ratings',
'team',
)
+ .order_by('-average_score')
)
context['addons'] = q.filter(type=EXTENSION_TYPE_CHOICES.BPY)[:8]
context['themes'] = q.filter(type=EXTENSION_TYPE_CHOICES.THEME)[:8]
@@ -66,6 +69,17 @@ def extension_version_download(request, type_slug, slug, version, filename):
class SearchView(ListedExtensionsView):
paginate_by = 16
template_name = 'extensions/list.html'
+ default_sort_by = '-average_score'
+ sort_by_options = OrderedDict(
+ [
+ ('-average_score', _('Rating')),
+ ('-download_count', _('Downloads')),
+ ('-date_approved', _('Newest First')),
+ ('date_approved', _('Oldest First')),
+ ('name', _('Title (A-Z)')),
+ ('-name', _('Title (Z-A)')),
+ ]
+ )
def _get_type_id_by_slug(self):
return next(k for k, v in EXTENSION_TYPE_SLUGS.items() if v == self.kwargs['type_slug'])
@@ -73,6 +87,12 @@ class SearchView(ListedExtensionsView):
def _get_type_by_slug(self):
return EXTENSION_TYPE_PLURAL[self._get_type_id_by_slug()]
+ def _get_sort_by(self):
+ sort_by = self.request.GET.get('sort_by', self.default_sort_by)
+ if sort_by not in self.sort_by_options:
+ sort_by = self.default_sort_by
+ return sort_by
+
def get_queryset(self):
queryset = super().get_queryset()
if self.kwargs.get('tag_slug'):
@@ -107,7 +127,7 @@ class SearchView(ListedExtensionsView):
'preview_set__file',
'ratings',
'team',
- )
+ ).order_by(self._get_sort_by())
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -121,12 +141,39 @@ class SearchView(ListedExtensionsView):
if self.kwargs.get('team_slug'):
context['team'] = get_object_or_404(teams.models.Team, slug=self.kwargs['team_slug'])
+ sort_by = self._get_sort_by()
+ context['sort_by'] = sort_by
+ context['sort_by_option_name'] = self.sort_by_options.get(sort_by)
+ context['sort_by_options'] = self.sort_by_options
+
# Determine which tags to list depending on the context.
+ tag_type_id = None
if context.get('type'):
tag_type_id = self._get_type_id_by_slug()
- context['tags'] = Tag.objects.filter(type=tag_type_id).exclude(versions=None)
elif context.get('tag'):
tag_type_id = context['tag'].type
- context['tags'] = Tag.objects.filter(type=tag_type_id).exclude(versions=None)
+ if tag_type_id:
+ tags = [
+ {
+ 'count': t['count'],
+ 'name': t['latest_version__tags__name'],
+ 'slug': t['latest_version__tags__slug'],
+ }
+ for t in Extension.objects.listed.select_related('latest_version__tags')
+ .filter(latest_version__tags__type=tag_type_id)
+ .values('latest_version__tags__name', 'latest_version__tags__slug')
+ .annotate(count=Count('id'))
+ .order_by('latest_version__tags__name')
+ ]
+ context['tags'] = tags
+ if 'tag' in context:
+ # this is silly, but the list is short
+ tag_slug = context['tag'].slug
+ for t in tags:
+ if t['slug'] == tag_slug:
+ context['current_tag_count'] = t['count']
+ break
+
+ context['total_count'] = super().get_queryset().filter(type=tag_type_id).count()
return context