fix public ressources not being public
This commit is contained in:
@@ -1,9 +1,27 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
class IsOwnerOrReadPublic(BasePermission):
|
||||
"""
|
||||
Read-only access for anonymous users (public sessions only).
|
||||
Authenticated users can only mutate their own sessions.
|
||||
"""
|
||||
def has_permission(self, request, view):
|
||||
if request.method in SAFE_METHODS:
|
||||
return True
|
||||
return request.user.is_authenticated
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in SAFE_METHODS:
|
||||
return obj.is_public or (
|
||||
request.user.is_authenticated and obj.user == request.user
|
||||
)
|
||||
return request.user.is_authenticated and obj.user == request.user
|
||||
|
||||
from .ballistics import compute_corrections
|
||||
from .models import FreePracticeSession, PRSSession, PRSStage, SpeedShootingSession
|
||||
from .serializers import (
|
||||
@@ -23,17 +41,18 @@ from .serializers import (
|
||||
# ── PRS ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
class PRSSessionViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
permission_classes = [IsOwnerOrReadPublic]
|
||||
filterset_fields = ['date', 'is_public']
|
||||
search_fields = ['name', 'location', 'notes', 'competition_name']
|
||||
ordering_fields = ['date', 'created_at']
|
||||
|
||||
def get_queryset(self):
|
||||
qs = (
|
||||
PRSSession.objects
|
||||
.filter(user=self.request.user)
|
||||
.select_related('rig', 'ammo', 'reloaded_batch__recipe', 'reloaded_batch__powder', 'analysis')
|
||||
)
|
||||
user = self.request.user
|
||||
if not user.is_authenticated:
|
||||
qs = PRSSession.objects.filter(is_public=True)
|
||||
else:
|
||||
qs = PRSSession.objects.filter(user=user)
|
||||
qs = qs.select_related('rig', 'ammo', 'reloaded_batch__recipe', 'reloaded_batch__powder', 'analysis')
|
||||
if self.action != 'list':
|
||||
qs = qs.prefetch_related('stages')
|
||||
return qs
|
||||
@@ -94,15 +113,20 @@ class PRSSessionViewSet(viewsets.ModelViewSet):
|
||||
# ── Free Practice ─────────────────────────────────────────────────────────────
|
||||
|
||||
class FreePracticeSessionViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
permission_classes = [IsOwnerOrReadPublic]
|
||||
filterset_fields = ['date', 'is_public']
|
||||
search_fields = ['name', 'location', 'notes']
|
||||
ordering_fields = ['date', 'created_at']
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if not user.is_authenticated:
|
||||
return FreePracticeSession.objects.filter(is_public=True).select_related(
|
||||
'rig', 'ammo', 'reloaded_batch__recipe', 'reloaded_batch__powder', 'analysis'
|
||||
)
|
||||
return (
|
||||
FreePracticeSession.objects
|
||||
.filter(user=self.request.user)
|
||||
.filter(user=user)
|
||||
.select_related('rig', 'ammo', 'reloaded_batch__recipe', 'reloaded_batch__powder', 'analysis')
|
||||
)
|
||||
|
||||
@@ -120,15 +144,20 @@ class FreePracticeSessionViewSet(viewsets.ModelViewSet):
|
||||
# ── Speed Shooting ────────────────────────────────────────────────────────────
|
||||
|
||||
class SpeedShootingSessionViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
permission_classes = [IsOwnerOrReadPublic]
|
||||
filterset_fields = ['date', 'is_public']
|
||||
search_fields = ['name', 'location', 'notes', 'format']
|
||||
ordering_fields = ['date', 'created_at']
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if not user.is_authenticated:
|
||||
return SpeedShootingSession.objects.filter(is_public=True).select_related(
|
||||
'rig', 'ammo', 'reloaded_batch__recipe', 'reloaded_batch__powder', 'analysis'
|
||||
)
|
||||
return (
|
||||
SpeedShootingSession.objects
|
||||
.filter(user=self.request.user)
|
||||
.filter(user=user)
|
||||
.select_related('rig', 'ammo', 'reloaded_batch__recipe', 'reloaded_batch__powder', 'analysis')
|
||||
)
|
||||
|
||||
|
||||
@@ -171,6 +171,11 @@ class ChronographAnalysisDetailSerializer(serializers.ModelSerializer):
|
||||
read_only_fields = ['user', 'created_at', 'updated_at']
|
||||
|
||||
def validate(self, attrs):
|
||||
instance = ChronographAnalysis(**attrs)
|
||||
instance.clean()
|
||||
# For partial updates, fall back to the existing instance's name
|
||||
if self.instance is not None:
|
||||
name = attrs.get('name', self.instance.name)
|
||||
else:
|
||||
name = attrs.get('name')
|
||||
if not name or not str(name).strip():
|
||||
raise serializers.ValidationError({'name': 'Name may not be blank.'})
|
||||
return attrs
|
||||
|
||||
@@ -44,8 +44,8 @@ services:
|
||||
volumes:
|
||||
- ./frontend:/usr/share/nginx/html:ro
|
||||
- ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
# ports:
|
||||
#- "5173:80"
|
||||
ports:
|
||||
- "5173:5000"
|
||||
depends_on:
|
||||
- app
|
||||
networks:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
const path = window.location.pathname;
|
||||
|
||||
const PROTECTED = ['/dashboard.html', '/gears.html', '/reloads.html',
|
||||
'/profile.html', '/sessions.html', '/admin.html',
|
||||
'/profile.html', '/admin.html',
|
||||
'/messages.html', '/friends.html'];
|
||||
const ADMIN_ONLY = ['/admin.html'];
|
||||
const AUTH_ONLY = ['/login.html', '/register.html'];
|
||||
|
||||
@@ -131,6 +131,9 @@ function renderDetail() {
|
||||
const s = currentSession;
|
||||
const titleLabel = s.competition_name || s.name || '(unnamed)';
|
||||
document.getElementById('detailTitle').textContent = titleLabel;
|
||||
// Hide write controls for anonymous viewers
|
||||
document.getElementById('togglePublicBtn').classList.toggle('d-none', !_isAuth);
|
||||
document.getElementById('deleteSessionBtn').classList.toggle('d-none', !_isAuth);
|
||||
document.getElementById('detailMeta').textContent =
|
||||
[s.date, s.location].filter(Boolean).join(' · ');
|
||||
|
||||
@@ -1080,5 +1083,10 @@ function esc(s) {
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
const _isAuth = !!getAccess();
|
||||
if (!_isAuth) {
|
||||
document.getElementById('newSessionBtn').classList.add('d-none');
|
||||
}
|
||||
|
||||
applyTranslations();
|
||||
loadSessions();
|
||||
|
||||
Reference in New Issue
Block a user