feat(event): option to disable an event
This commit is contained in:
@@ -16,7 +16,9 @@ from .wire import encode, to_epoch_ms
|
|||||||
class ClockConsumer(AsyncWebsocketConsumer):
|
class ClockConsumer(AsyncWebsocketConsumer):
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
self.event_path = self.scope["url_route"]["kwargs"]["event_path"]
|
self.event_path = self.scope["url_route"]["kwargs"]["event_path"]
|
||||||
self.event = await self._get_enabled_event(self.event_path)
|
self.event = await self._get_enabled_event(
|
||||||
|
self.event_path, self.scope.get("user")
|
||||||
|
)
|
||||||
if self.event is None:
|
if self.event is None:
|
||||||
await self.close()
|
await self.close()
|
||||||
return
|
return
|
||||||
@@ -62,8 +64,8 @@ class ClockConsumer(AsyncWebsocketConsumer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def _get_enabled_event(self, event_path):
|
def _get_enabled_event(self, event_path, user):
|
||||||
event = Event.objects.filter(path=event_path).first()
|
event = Event.objects.available_to(user).filter(path=event_path).first()
|
||||||
if event is None:
|
if event is None:
|
||||||
return None
|
return None
|
||||||
if not Game.objects.enabled_for_event(event).filter(path="clock").exists():
|
if not Game.objects.enabled_for_event(event).filter(path="clock").exists():
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ from .models import Event
|
|||||||
|
|
||||||
@admin.register(Event)
|
@admin.register(Event)
|
||||||
class EventAdmin(admin.ModelAdmin):
|
class EventAdmin(admin.ModelAdmin):
|
||||||
list_display = ["name", "path", "created_at"]
|
list_display = ["name", "path", "is_available", "created_at"]
|
||||||
|
list_editable = ["is_available"]
|
||||||
|
list_filter = ["is_available"]
|
||||||
search_fields = ["name", "path"]
|
search_fields = ["name", "path"]
|
||||||
prepopulated_fields = {"path": ("name",)}
|
prepopulated_fields = {"path": ("name",)}
|
||||||
readonly_fields = ["created_at", "updated_at"]
|
readonly_fields = ["created_at", "updated_at"]
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class EventContextMiddleware:
|
|||||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||||
event_path = view_kwargs.get("event_path")
|
event_path = view_kwargs.get("event_path")
|
||||||
if event_path:
|
if event_path:
|
||||||
request.event = get_object_or_404(Event, path=event_path)
|
request.event = get_object_or_404(
|
||||||
|
Event.objects.available_to(getattr(request, "user", None)),
|
||||||
|
path=event_path,
|
||||||
|
)
|
||||||
current_event.set(request.event)
|
current_event.set(request.event)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-10-15 22:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("events", "0002_seed_polylan_43"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="event",
|
||||||
|
name="is_available",
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -14,9 +14,21 @@ class BaseModel(models.Model):
|
|||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class EventQuerySet(models.QuerySet):
|
||||||
|
def available_to(self, user):
|
||||||
|
"""Unavailable events are hidden from everyone but staff, so they can
|
||||||
|
still be prepared before being opened up."""
|
||||||
|
if user is not None and user.is_staff:
|
||||||
|
return self
|
||||||
|
return self.filter(is_available=True)
|
||||||
|
|
||||||
|
|
||||||
class Event(BaseModel):
|
class Event(BaseModel):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
path = models.SlugField(max_length=100, unique=True)
|
path = models.SlugField(max_length=100, unique=True)
|
||||||
|
is_available = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
objects = EventQuerySet.as_manager()
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
@@ -32,3 +34,41 @@ class EventScopedManagerTests(TestCase):
|
|||||||
current_event.reset(token)
|
current_event.reset(token)
|
||||||
|
|
||||||
self.assertEqual(results, [self.submission_a])
|
self.assertEqual(results, [self.submission_a])
|
||||||
|
|
||||||
|
|
||||||
|
class EventAvailabilityTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
from accounts.models import CustomUser
|
||||||
|
|
||||||
|
self.hidden = Event.objects.create(
|
||||||
|
name="Hidden", path="hidden", is_available=False
|
||||||
|
)
|
||||||
|
self.user = CustomUser.objects.create_user(username="user", password="x")
|
||||||
|
self.staff = CustomUser.objects.create_user(
|
||||||
|
username="staff", password="x", is_staff=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_unavailable_event_is_404_for_regular_user(self):
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
response = self.client.get(f"/{self.hidden.path}/market")
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_unavailable_event_is_visible_to_staff(self):
|
||||||
|
self.client.force_login(self.staff)
|
||||||
|
response = self.client.get(f"/{self.hidden.path}/market")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_available_to_filters_for_regular_user_only(self):
|
||||||
|
self.assertNotIn(self.hidden, Event.objects.available_to(self.user))
|
||||||
|
self.assertIn(self.hidden, Event.objects.available_to(self.staff))
|
||||||
|
|
||||||
|
def test_home_prefers_available_event_for_staff(self):
|
||||||
|
available = Event.objects.create(name="Open", path="open")
|
||||||
|
Event.objects.filter(pk=self.hidden.pk).update(
|
||||||
|
created_at=available.created_at + timedelta(days=1)
|
||||||
|
)
|
||||||
|
self.client.force_login(self.staff)
|
||||||
|
response = self.client.get("/")
|
||||||
|
self.assertRedirects(
|
||||||
|
response, f"/{available.path}/", fetch_redirect_response=False
|
||||||
|
)
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ from .wire import encode, to_epoch_ms
|
|||||||
class PlaceConsumer(AsyncWebsocketConsumer):
|
class PlaceConsumer(AsyncWebsocketConsumer):
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
self.event_path = self.scope["url_route"]["kwargs"]["event_path"]
|
self.event_path = self.scope["url_route"]["kwargs"]["event_path"]
|
||||||
self.event = await self._get_enabled_event(self.event_path)
|
self.event = await self._get_enabled_event(
|
||||||
|
self.event_path, self.scope.get("user")
|
||||||
|
)
|
||||||
if self.event is None:
|
if self.event is None:
|
||||||
await self.close()
|
await self.close()
|
||||||
return
|
return
|
||||||
@@ -80,8 +82,8 @@ class PlaceConsumer(AsyncWebsocketConsumer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def _get_enabled_event(self, event_path):
|
def _get_enabled_event(self, event_path, user):
|
||||||
event = Event.objects.filter(path=event_path).first()
|
event = Event.objects.available_to(user).filter(path=event_path).first()
|
||||||
if event is None:
|
if event is None:
|
||||||
return None
|
return None
|
||||||
if not Game.objects.enabled_for_event(event).filter(path="place").exists():
|
if not Game.objects.enabled_for_event(event).filter(path="place").exists():
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ OPUS_MAGNUM_APP_ID = 558990
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def home(request: HttpRequest):
|
def home(request: HttpRequest):
|
||||||
event = Event.objects.order_by("-created_at").first()
|
# Staff land on an available event too; hidden ones are only a fallback.
|
||||||
|
events = Event.objects.available_to(request.user)
|
||||||
|
event = events.order_by("-is_available", "-created_at").first()
|
||||||
if event is None:
|
if event is None:
|
||||||
raise Http404("No event configured")
|
raise Http404("No event configured")
|
||||||
return redirect("event.home", event_path=event.path)
|
return redirect("event.home", event_path=event.path)
|
||||||
|
|||||||
Reference in New Issue
Block a user