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 %}
- {% endif %} + +
{% 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