feat(market): enable/disable like any other games

This commit is contained in:
2026-10-16 00:23:27 +02:00
parent 107fe8b69b
commit 919e611895
17 changed files with 112 additions and 97 deletions
+9
View File
@@ -1,11 +1,13 @@
from typing import List
from ninja import Router
from ninja.decorators import decorate_view
from ninja.errors import HttpError
from django.shortcuts import get_object_or_404
from django.db.models import Sum, Prefetch
from django.db.models.functions import Coalesce
from django.db import transaction
from games.decorators import require_game_enabled
from market.models import (
Market,
MarketOption,
@@ -29,6 +31,7 @@ PREFIX = "/events/{event_path}/market"
@router.get(f"{PREFIX}/", response=List[MarketListSchema])
@decorate_view(require_game_enabled(path="market"))
def list_markets(request, event_path: str):
"""List all markets (excludes draft markets)."""
markets = Market.for_event.exclude(status=Market.Status.DRAFT)
@@ -41,6 +44,7 @@ def list_markets(request, event_path: str):
@router.get(f"{PREFIX}/user/bets", response=List[UserBetSchema])
@decorate_view(require_game_enabled(path="market"))
def list_user_bets(request, event_path: str):
"""List all bets placed by the current user."""
if not request.user.is_authenticated:
@@ -54,6 +58,7 @@ def list_user_bets(request, event_path: str):
@router.get(f"{PREFIX}/user/points", response=UserPointsSchema)
@decorate_view(require_game_enabled(path="market"))
def get_user_points(request, event_path: str):
"""Get the current user's points balance for this event."""
if not request.user.is_authenticated:
@@ -63,6 +68,7 @@ def get_user_points(request, event_path: str):
@router.get(f"{PREFIX}/{{market_uuid}}/history", response=List[BetHistoryPointSchema])
@decorate_view(require_game_enabled(path="market"))
def bet_history(request, event_path: str, market_uuid: str):
"""Points added to each option over time (anonymous, one entry per bet placed/increased)."""
market = get_object_or_404(
@@ -93,6 +99,7 @@ def bet_history(request, event_path: str, market_uuid: str):
@router.post(f"{PREFIX}/{{market_uuid}}/actions/close")
@decorate_view(require_game_enabled(path="market"))
def close_market(request, event_path: str, market_uuid: str):
"""Close a market. Admin only."""
if not request.user.is_staff:
@@ -105,6 +112,7 @@ def close_market(request, event_path: str, market_uuid: str):
@router.post(f"{PREFIX}/{{market_uuid}}/actions/resolve", response=MarketListSchema)
@decorate_view(require_game_enabled(path="market"))
def resolve_market(
request, event_path: str, market_uuid: str, payload: ResolveMarketSchema
):
@@ -172,6 +180,7 @@ def resolve_market(
@router.post(f"{PREFIX}/{{market_uuid}}/bets", response=UserBetSchema)
@decorate_view(require_game_enabled(path="market"))
def create_bet(
request, event_path: str, market_uuid: str, payload: UserBetCreateSchema
):
@@ -0,0 +1,40 @@
from django.db import migrations
MARKET_PATH = "market"
def seed_market_game(apps, schema_editor):
"""The market used to be toggled globally (MARKET_ENABLED); it's now a
custom Game like clock/place, enabled per event through EventGame. Enable
it for every existing event so nothing disappears on deploy."""
Event = apps.get_model("events", "Event")
Game = apps.get_model("games", "Game")
EventGame = apps.get_model("games", "EventGame")
game, _ = Game.objects.get_or_create(
path=MARKET_PATH,
custom_path=True,
defaults={
"name": "Market",
"description": "Place your bets and compete",
},
)
for event in Event.objects.all():
EventGame.objects.get_or_create(event=event, game=game)
def unseed_market_game(apps, schema_editor):
Game = apps.get_model("games", "Game")
Game.objects.filter(path=MARKET_PATH, custom_path=True).delete()
class Migration(migrations.Migration):
dependencies = [
("events", "0003_event_is_available"),
("games", "0009_game_description"),
("market", "0009_event_required"),
]
operations = [
migrations.RunPython(seed_market_game, reverse_code=unseed_market_game),
]
+23 -1
View File
@@ -1 +1,23 @@
# Create your tests here.
from django.test import TestCase
from accounts.models import CustomUser
from events.models import Event
from games.models import EventGame, Game
class MarketGameGatingTests(TestCase):
def setUp(self):
self.event = Event.objects.create(name="Event", path="event")
self.game = Game.objects.get(path="market", custom_path=True)
self.user = CustomUser.objects.create_user(username="user", password="x")
self.client.force_login(self.user)
def test_market_is_403_when_not_enabled_for_event(self):
response = self.client.get(f"/api/events/{self.event.path}/market/")
self.assertEqual(response.status_code, 403)
def test_market_is_reachable_when_enabled_for_event(self):
EventGame.objects.create(event=self.event, game=self.game)
response = self.client.get(f"/api/events/{self.event.path}/market/")
self.assertEqual(response.status_code, 200)
self.assertEqual(self.client.get(f"/{self.event.path}/market").status_code, 200)
+2
View File
@@ -3,9 +3,11 @@ from django.shortcuts import get_object_or_404, render
from django.http import HttpRequest
from events.models import Event
from games.decorators import require_game_enabled
@login_required
@require_game_enabled(path="market")
def market_home(request: HttpRequest, event_path: str):
get_object_or_404(Event, path=event_path)
return render(request, "app.html")