Extension page should be 404 for everyone unless publicly listed #167

Merged
Anna Sirota merged 5 commits from extension-detail-page-listed-only into main 2024-06-05 12:12:14 +02:00
4 changed files with 100 additions and 51 deletions

View File

@ -5,7 +5,7 @@ from django.urls import reverse
from common.tests.factories.extensions import create_version, create_approved_version from common.tests.factories.extensions import create_version, create_approved_version
from common.tests.factories.teams import TeamFactory from common.tests.factories.teams import TeamFactory
from common.tests.factories.users import UserFactory from common.tests.factories.users import UserFactory, create_moderator
from extensions.models import Extension, Version from extensions.models import Extension, Version
from files.models import File from files.models import File
from teams.models import Team, TeamsUsers from teams.models import Team, TeamsUsers
@ -90,6 +90,54 @@ class PublicViewsTest(_BaseTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'extensions/home.html') self.assertTemplateUsed(response, 'extensions/home.html')
def test_no_one_can_view_extension_page_when_not_listed_404(self):
extension = create_version(extension__is_listed=False).extension
moderator = create_moderator()
staff = UserFactory(is_staff=True)
superuser = UserFactory(is_superuser=True, is_staff=True)
author = extension.authors.first()
url = extension.get_absolute_url()
response = self.client.get(url)
for account, role in (
(None, 'anonymous'),
(moderator, 'moderator'),
(staff, 'staff'),
(superuser, 'superuser'),
(author, 'author'),
):
with self.subTest(account=account, role=role):
self.client.logout()
if account:
self.client.force_login(account)
self.assertEqual(response.status_code, 404)
def test_anyone_can_view_extension_page_when_listed(self):
extension = create_approved_version().extension
moderator = create_moderator()
staff = UserFactory(is_staff=True)
superuser = UserFactory(is_superuser=True, is_staff=True)
author = extension.authors.first()
url = extension.get_absolute_url()
response = self.client.get(url)
for account, role in (
(None, 'anonymous'),
(moderator, 'moderator'),
(staff, 'staff'),
(superuser, 'superuser'),
(author, 'author'),
):
with self.subTest(role=role):
self.client.logout()
if account:
self.client.force_login(account)
self.assertEqual(response.status_code, 200)
class ApiViewsTest(_BaseTestCase): class ApiViewsTest(_BaseTestCase):
def test_blender_version_filter(self): def test_blender_version_filter(self):

View File

@ -28,6 +28,14 @@ urlpatterns = [
path('search/', public.SearchView.as_view(), name='search'), path('search/', public.SearchView.as_view(), name='search'),
path('tag/<slug:tag_slug>/', public.SearchView.as_view(), name='by-tag'), path('tag/<slug:tag_slug>/', public.SearchView.as_view(), name='by-tag'),
path('team/<slug:team_slug>/', public.SearchView.as_view(), name='by-team'), path('team/<slug:team_slug>/', public.SearchView.as_view(), name='by-team'),
re_path(
rf'^(?P<type_slug>{EXTENSION_SLUGS_PATH})/',
include(
[
path('<slug:slug>/', public.ExtensionDetailView.as_view(), name='detail'),
]
),
),
re_path( re_path(
rf'^(?P<type_slug>{EXTENSION_SLUGS_PATH})/$', rf'^(?P<type_slug>{EXTENSION_SLUGS_PATH})/$',
public.SearchView.as_view(), public.SearchView.as_view(),
@ -84,7 +92,6 @@ urlpatterns = [
name='version-download', name='version-download',
), ),
path('<slug:slug>/versions/', manage.VersionsView.as_view(), name='versions'), path('<slug:slug>/versions/', manage.VersionsView.as_view(), name='versions'),
path('<slug:slug>/', manage.ExtensionDetailView.as_view(), name='detail'),
], ],
), ),
), ),

View File

@ -3,7 +3,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db import transaction from django.db import transaction
from django.shortcuts import get_object_or_404, redirect, reverse from django.shortcuts import get_object_or_404, redirect, reverse
from django.views.generic import DetailView, ListView from django.views.generic import ListView
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
@ -22,52 +22,6 @@ from extensions.forms import (
from extensions.models import Extension, Version from extensions.models import Extension, Version
from files.forms import FileForm from files.forms import FileForm
from files.models import File from files.models import File
from stats.models import ExtensionView
import ratings.models
class ExtensionDetailView(ExtensionQuerysetMixin, DetailView):
model = Extension
context_object_name = 'extension'
template_name = 'extensions/detail.html'
def get_queryset(self):
"""Allow logged in users to view unlisted add-ons in certain conditions.
* maintainers should be able to preview their yet unlisted add-ons;
* staff should be able to preview yet unlisted add-ons;
"""
return self.get_extension_queryset().prefetch_related(
'authors',
'ratings',
'ratings__user',
'versions',
'versions__file',
'versions__file__validation',
'versions__permissions',
'versions__platforms',
)
def get_object(self, queryset=None):
"""Record a page view when returning the Extension object."""
obj = super().get_object(queryset=queryset)
if obj.is_listed and (
self.request.user.is_anonymous or not obj.has_maintainer(self.request.user)
):
ExtensionView.create_from_request(self.request, object_id=obj.pk)
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.user.is_authenticated:
context['my_rating'] = ratings.models.Rating.get_for(
self.request.user.pk, self.object.pk
)
extension = context['object']
# Add the image for "og:image" meta to the context
if extension.featured_image and extension.featured_image.is_listed:
context['default_image_path'] = extension.featured_image.thumbnail_1080p_url
return context
class VersionsView(ExtensionQuerysetMixin, ListView): class VersionsView(ExtensionQuerysetMixin, ListView):

View File

@ -6,8 +6,7 @@ from django.db import connection
from django.db.models import Count, 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.utils.translation import gettext_lazy as _
from django.views.generic.list import ListView from django.views.generic import DetailView, ListView
from extensions.models import Extension, Version, Tag from extensions.models import Extension, Version, Tag
from constants.base import ( from constants.base import (
@ -15,6 +14,8 @@ from constants.base import (
EXTENSION_TYPE_PLURAL, EXTENSION_TYPE_PLURAL,
EXTENSION_TYPE_CHOICES, EXTENSION_TYPE_CHOICES,
) )
from stats.models import ExtensionView
import ratings.models
from stats.models import ExtensionDownload, VersionDownload from stats.models import ExtensionDownload, VersionDownload
import teams.models import teams.models
@ -219,3 +220,42 @@ class SearchView(ListedExtensionsView):
context['total_count'] = super().get_queryset().filter(type=tag_type_id).count() context['total_count'] = super().get_queryset().filter(type=tag_type_id).count()
return context return context
class ExtensionDetailView(DetailView):
queryset = Extension.objects.listed.prefetch_related(
'authors',
'latest_version__file',
'latest_version__tags',
'preview_set',
'preview_set__file',
'ratings',
'ratings__user',
'team',
'versions',
'versions__file',
'versions__file__validation',
'versions__permissions',
'versions__platforms',
).distinct()
context_object_name = 'extension'
template_name = 'extensions/detail.html'
def get_object(self, queryset=None):
"""Record a page view when returning the Extension object."""
obj = super().get_object(queryset=queryset)
if self.request.user.is_anonymous or not obj.has_maintainer(self.request.user):
ExtensionView.create_from_request(self.request, object_id=obj.pk)
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.user.is_authenticated:
context['my_rating'] = ratings.models.Rating.get_for(
self.request.user.pk, self.object.pk
)
extension = context['object']
# Add the image for "og:image" meta to the context
if extension.featured_image and extension.featured_image.is_listed:
context['default_image_path'] = extension.featured_image.thumbnail_1080p_url
return context