extensions-website/extensions/views/public.py
Anna Sirota caae613747 Make it possible to fully delete unlisted/unrated extensions and versions (#81)
* removes all soft-deletion;
* shows a "Delete extension" button on the draft page in case it can be deleted;
* shows a "Delete version" button on the version page in case it can be deleted;
* a version can be deleted if
  * its file isn't approved, and it doesn't have any ratings;
* an extension can be deleted if
  * it's not listed, and doesn't have any ratings or abuse reports;
  * all it's versions can also be deleted;
* changes default `File.status` from `APPROVED` to `AWAITING_REVIEW`
  With version's file status being `APPROVED` by default, a version can never be deleted, even when the extension is still a draft.
  This change doesn't affect the approval process because
   * when an extension is approved its latest version becomes approved automatically (no change here);
   * when a new version is uploaded to an approved extension, it's approved automatically (this is new).

This allows authors to delete their drafts, freeing the extension slug and making it possible to re-upload the same file.
This also makes it possible to easily fix mistakes during the drafting of a new extension (e.g. delete a version and re-upload it without bumping a version for each typo/mistake in packaging and so on).
(see #78 and #63)

Reviewed-on: #81
2024-04-19 11:00:13 +02:00

118 lines
4.3 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
from .api import ExtensionsAPIView
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 = 15
template_name = 'extensions/home.html'
def dispatch(self, request, *args, **kwargs):
"""Return the API view if requesting a JSON."""
if request.headers.get('Accept') == 'application/json':
api_view = ExtensionsAPIView.as_view()
return api_view(request, *args, **kwargs)
else:
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
q = super().get_queryset()
context['addons'] = q.filter(type=EXTENSION_TYPE_CHOICES.BPY).order_by('-average_score')[:8]
context['themes'] = q.filter(type=EXTENSION_TYPE_CHOICES.THEME).order_by('-average_score')[
:8
]
return context
def extension_version_download(request, type_slug, slug, version):
"""Download an extension version and count downloads."""
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)
class SearchView(ListedExtensionsView):
paginate_by = 15
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(versions__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(versions__tags__name__icontains=token)
)
queryset = queryset.filter(search_query).distinct()
return queryset
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)
elif context.get('tag'):
tag_type_id = context['tag'].type
context['tags'] = Tag.objects.filter(type=tag_type_id)
return context