diff --git a/extensions/tests/test_views.py b/extensions/tests/test_views.py index 0c4e18a5..6698b9e8 100644 --- a/extensions/tests/test_views.py +++ b/extensions/tests/test_views.py @@ -5,7 +5,7 @@ from django.urls import reverse from common.tests.factories.extensions import create_version, create_approved_version 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 files.models import File from teams.models import Team, TeamsUsers @@ -90,6 +90,54 @@ class PublicViewsTest(_BaseTestCase): self.assertEqual(response.status_code, 200) 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): def test_blender_version_filter(self): diff --git a/extensions/urls.py b/extensions/urls.py index e3531431..a2c63b8e 100644 --- a/extensions/urls.py +++ b/extensions/urls.py @@ -28,6 +28,14 @@ urlpatterns = [ path('search/', public.SearchView.as_view(), name='search'), path('tag//', public.SearchView.as_view(), name='by-tag'), path('team//', public.SearchView.as_view(), name='by-team'), + re_path( + rf'^(?P{EXTENSION_SLUGS_PATH})/', + include( + [ + path('/', public.ExtensionDetailView.as_view(), name='detail'), + ] + ), + ), re_path( rf'^(?P{EXTENSION_SLUGS_PATH})/$', public.SearchView.as_view(), @@ -84,7 +92,6 @@ urlpatterns = [ name='version-download', ), path('/versions/', manage.VersionsView.as_view(), name='versions'), - path('/', manage.ExtensionDetailView.as_view(), name='detail'), ], ), ), diff --git a/extensions/views/manage.py b/extensions/views/manage.py index 47f532c9..85247ce2 100644 --- a/extensions/views/manage.py +++ b/extensions/views/manage.py @@ -3,7 +3,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.messages.views import SuccessMessageMixin from django.db import transaction 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 @@ -22,52 +22,6 @@ from extensions.forms import ( from extensions.models import Extension, Version from files.forms import FileForm 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): diff --git a/extensions/views/public.py b/extensions/views/public.py index ca299472..aceca8de 100644 --- a/extensions/views/public.py +++ b/extensions/views/public.py @@ -6,8 +6,7 @@ from django.db import connection 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 - +from django.views.generic import DetailView, ListView from extensions.models import Extension, Version, Tag from constants.base import ( @@ -15,6 +14,8 @@ from constants.base import ( EXTENSION_TYPE_PLURAL, EXTENSION_TYPE_CHOICES, ) +from stats.models import ExtensionView +import ratings.models from stats.models import ExtensionDownload, VersionDownload import teams.models @@ -219,3 +220,42 @@ class SearchView(ListedExtensionsView): context['total_count'] = super().get_queryset().filter(type=tag_type_id).count() 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