Extensions list: sort_by parameter #159
@ -133,7 +133,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,14 @@
|
|||||||
display: flex
|
display: flex
|
||||||
+padding(2)
|
+padding(2)
|
||||||
|
|
||||||
|
.badge
|
||||||
|
+margin(3, left)
|
||||||
|
pointer-events: none
|
||||||
|
|
||||||
|
.dropdown-item,
|
||||||
|
.dropdown-toggle
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
+media-md
|
+media-md
|
||||||
.dropdown-menu-filter
|
.dropdown-menu-filter
|
||||||
gap: var(--spacer-1)
|
gap: var(--spacer-1)
|
||||||
@ -26,6 +34,7 @@
|
|||||||
|
|
||||||
.dropdown-menu-filter-sort
|
.dropdown-menu-filter-sort
|
||||||
max-height: calc(var(--spacer) * 24.25)
|
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
|
overflow: auto
|
||||||
|
|
||||||
.dropdown-item
|
.dropdown-item
|
||||||
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -220,6 +220,12 @@ class Extension(CreatedModifiedMixin, RatingMixin, TrackChangesMixin, models.Mod
|
|||||||
objects = ExtensionManager()
|
objects = ExtensionManager()
|
||||||
|
|
||||||
class Meta:
|
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']
|
ordering = ['-average_score', '-date_created', 'name']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% extends "common/base.html" %}
|
{% extends "common/base.html" %}
|
||||||
{% load i18n %}
|
{% load common i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% include "extensions/components/listing_title.html" %}{% endblock page_title %}
|
{% block page_title %}{% include "extensions/components/listing_title.html" %}{% endblock page_title %}
|
||||||
|
|
||||||
@ -21,26 +21,15 @@
|
|||||||
</h2>
|
</h2>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if tags %}
|
<div class="btn-row align-items-center">
|
||||||
<div class="d-flex flex-column flex-md-row">
|
<span class="text-secondary">Filter</span>
|
||||||
|
{% if tags %}
|
||||||
<div class="dropdown dropdown-filter-sort">
|
<div class="dropdown dropdown-filter-sort">
|
||||||
<button class="align-items-center d-flex dropdown-toggle js-dropdown-toggle" data-toggle-menu-id="js-dropdown-menu-filter">
|
<button class="dropdown-toggle js-dropdown-toggle" data-toggle-menu-id="js-dropdown-menu-filter">
|
||||||
{% if tag %}
|
{% if tag %}
|
||||||
{{ tag.name }}
|
{{ tag.name }}
|
||||||
{# TODO: @back-end add tags count dynamic #}
|
|
||||||
{% comment %}
|
|
||||||
<div class="align-items-center bg-tertiary d-flex h-4 fs-xs justify-content-center ms-2 rounded-circle w-4">
|
|
||||||
1
|
|
||||||
</div>
|
|
||||||
{% endcomment %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
All
|
All
|
||||||
{# TODO: @back-end add tags count dynamic #}
|
|
||||||
{% comment %}
|
|
||||||
<div class="align-items-center bg-tertiary d-flex h-4 fs-xs justify-content-center ms-2 rounded-circle w-4">
|
|
||||||
1
|
|
||||||
</div>
|
|
||||||
{% endcomment %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<i class="i-chevron-down"></i>
|
<i class="i-chevron-down"></i>
|
||||||
@ -51,11 +40,9 @@
|
|||||||
{# TODO @back-end: Find a proper way to get the plural tag type to build the URL. #}
|
{# TODO @back-end: Find a proper way to get the plural tag type to build the URL. #}
|
||||||
<a class="dropdown-item {% if not tag.name %}is-active{% endif %}" href="/{{ tag.get_type_display|slugify }}s/">
|
<a class="dropdown-item {% if not tag.name %}is-active{% endif %}" href="/{{ tag.get_type_display|slugify }}s/">
|
||||||
All
|
All
|
||||||
{% comment %}
|
<div class="badge">
|
||||||
<div class="align-items-center bg-tertiary d-flex h-4 fs-xs justify-content-center ms-2 rounded-circle w-4">
|
{{ total_count }}
|
||||||
1
|
|
||||||
</div>
|
</div>
|
||||||
{% endcomment %}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@ -63,19 +50,35 @@
|
|||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item {% if tag.name == list_tag.name %}is-active{% endif %}" href="{% url "extensions:by-tag" tag_slug=list_tag.slug %}" title="{{ list_tag.name }}">
|
<a class="dropdown-item {% if tag.name == list_tag.name %}is-active{% endif %}" href="{% url "extensions:by-tag" tag_slug=list_tag.slug %}" title="{{ list_tag.name }}">
|
||||||
{{ list_tag.name }}
|
{{ list_tag.name }}
|
||||||
|
<div class="badge">
|
||||||
{% comment %}
|
{{list_tag.count}}
|
||||||
<div class="align-items-center bg-tertiary d-flex h-4 fs-xs justify-content-center ms-2 rounded-circle w-4">
|
|
||||||
1
|
|
||||||
</div>
|
</div>
|
||||||
{% endcomment %}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center ms-3">
|
||||||
|
<span class="text-secondary">Sort By</span>
|
||||||
|
<div class="dropdown dropdown-filter-sort ms-3">
|
||||||
|
<button class="dropdown-toggle js-dropdown-toggle" data-toggle-menu-id="js-dropdown-menu-sort">
|
||||||
|
{{ sort_by_option_name }} <i class="i-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-filter-sort dropdown-menu-right js-dropdown-menu" id="js-dropdown-menu-sort">
|
||||||
|
{% for option_sort_by, name in sort_by_options.items %}
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item{% if option_sort_by == sort_by %} is-active{% endif %}" href="?{% query_transform sort_by=option_sort_by %}">
|
||||||
|
{{ name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
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.shortcuts import get_object_or_404, redirect
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +46,7 @@ class HomeView(ListedExtensionsView):
|
|||||||
'ratings',
|
'ratings',
|
||||||
'team',
|
'team',
|
||||||
)
|
)
|
||||||
|
.order_by('-average_score')
|
||||||
)
|
)
|
||||||
context['addons'] = q.filter(type=EXTENSION_TYPE_CHOICES.BPY)[:8]
|
context['addons'] = q.filter(type=EXTENSION_TYPE_CHOICES.BPY)[:8]
|
||||||
context['themes'] = q.filter(type=EXTENSION_TYPE_CHOICES.THEME)[: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):
|
class SearchView(ListedExtensionsView):
|
||||||
paginate_by = 16
|
paginate_by = 16
|
||||||
template_name = 'extensions/list.html'
|
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):
|
def _get_type_id_by_slug(self):
|
||||||
return next(k for k, v in EXTENSION_TYPE_SLUGS.items() if v == self.kwargs['type_slug'])
|
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):
|
def _get_type_by_slug(self):
|
||||||
return EXTENSION_TYPE_PLURAL[self._get_type_id_by_slug()]
|
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):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
if self.kwargs.get('tag_slug'):
|
if self.kwargs.get('tag_slug'):
|
||||||
@ -107,7 +127,7 @@ class SearchView(ListedExtensionsView):
|
|||||||
'preview_set__file',
|
'preview_set__file',
|
||||||
'ratings',
|
'ratings',
|
||||||
'team',
|
'team',
|
||||||
)
|
).order_by(self._get_sort_by())
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
@ -121,12 +141,39 @@ class SearchView(ListedExtensionsView):
|
|||||||
if self.kwargs.get('team_slug'):
|
if self.kwargs.get('team_slug'):
|
||||||
context['team'] = get_object_or_404(teams.models.Team, slug=self.kwargs['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.
|
# Determine which tags to list depending on the context.
|
||||||
|
tag_type_id = None
|
||||||
if context.get('type'):
|
if context.get('type'):
|
||||||
tag_type_id = self._get_type_id_by_slug()
|
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'):
|
elif context.get('tag'):
|
||||||
tag_type_id = context['tag'].type
|
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
|
return context
|
||||||
|
Loading…
Reference in New Issue
Block a user