extensions-website/extensions/views/public.py
Oleg Komarov db67d94507 Use a materialized Extension.latest_version field instead of a dynamic property (#152)
Original reason for this change is #128: we need an efficient way to query tags of a latest_version.

We could potentially avoid converting this property to a field if we had a proper search engine,
but we would still need to define the same explicit triggers for reindexing - i.e. recompute the latest_version change.

This PR also takes a stab at simplifying data flow,
but more work is needed to improve the management of `is_listed` and `status` fields.

Reviewed-on: #152
Reviewed-by: Anna Sirota <railla@noreply.localhost>
2024-05-27 17:58:54 +02:00

133 lines
4.7 KiB
Python

import logging
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect
from django.views.generic.list import ListView
from extensions.models import Extension, Version, Tag
from constants.base import (
EXTENSION_TYPE_SLUGS,
EXTENSION_TYPE_PLURAL,
EXTENSION_TYPE_CHOICES,
)
from stats.models import ExtensionDownload, VersionDownload
import teams.models
User = get_user_model()
log = logging.getLogger(__name__)
class ListedExtensionsView(ListView):
model = Extension
queryset = Extension.objects.listed
context_object_name = 'extensions'
class HomeView(ListedExtensionsView):
paginate_by = 16
template_name = 'extensions/home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
q = (
super()
.get_queryset()
.prefetch_related(
'authors',
'latest_version__file',
'latest_version__tags',
'preview_set',
'preview_set__file',
'ratings',
'team',
)
)
context['addons'] = q.filter(type=EXTENSION_TYPE_CHOICES.BPY)[:8]
context['themes'] = q.filter(type=EXTENSION_TYPE_CHOICES.THEME)[:8]
return context
def extension_version_download(request, type_slug, slug, version, filename):
"""Download an extension version and count downloads.
The `filename` parameter is used to pass a file name ending with `.zip`.
This is a convention Blender uses to initiate an extension installation on an HTML anchor
drag&drop.
"""
extension_version = get_object_or_404(Version, extension__slug=slug, version=version)
ExtensionDownload.create_from_request(request, object_id=extension_version.extension_id)
VersionDownload.create_from_request(request, object_id=extension_version.pk)
return redirect(extension_version.downloadable_signed_url + f'?filename={filename}')
class SearchView(ListedExtensionsView):
paginate_by = 16
template_name = 'extensions/list.html'
def _get_type_id_by_slug(self):
return next(k for k, v in EXTENSION_TYPE_SLUGS.items() if v == self.kwargs['type_slug'])
def _get_type_by_slug(self):
return EXTENSION_TYPE_PLURAL[self._get_type_id_by_slug()]
def get_queryset(self):
queryset = super().get_queryset()
if self.kwargs.get('tag_slug'):
queryset = queryset.filter(
latest_version__tags__slug=self.kwargs['tag_slug']
).distinct()
if self.kwargs.get('team_slug'):
queryset = queryset.filter(team__slug=self.kwargs['team_slug'])
if self.kwargs.get('user_id'):
queryset = queryset.filter(
authors__maintainer__user_id=self.kwargs['user_id']
).distinct()
if self.kwargs.get('type_slug'):
_type = self._get_type_id_by_slug()
queryset = queryset.filter(type=_type)
if 'q' in self.request.GET:
qs = self.request.GET['q'].split()
search_query = Q()
for token in qs:
search_query &= (
Q(slug__icontains=token)
| Q(name__icontains=token)
| Q(description__icontains=token)
| Q(latest_version__tags__name__icontains=token)
)
queryset = queryset.filter(search_query).distinct()
return queryset.prefetch_related(
'authors',
'latest_version__file',
'latest_version__tags',
'preview_set',
'preview_set__file',
'ratings',
'team',
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.kwargs.get('user_id'):
context['author'] = get_object_or_404(User, pk=self.kwargs['user_id'])
if self.kwargs.get('tag_slug'):
context['tag'] = get_object_or_404(Tag, slug=self.kwargs['tag_slug'])
if self.kwargs.get('type_slug'):
context['type'] = self._get_type_by_slug()
if self.kwargs.get('team_slug'):
context['team'] = get_object_or_404(teams.models.Team, slug=self.kwargs['team_slug'])
# Determine which tags to list depending on the context.
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)
return context