From ce305398080c394dd2e1dc601f829c54fd159a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gremaud?= Date: Sat, 23 May 2026 17:48:27 +0200 Subject: [PATCH] feat(market): base models --- polylan_submitter/market/__init__.py | 0 polylan_submitter/market/admin.py | 3 + polylan_submitter/market/apps.py | 6 + .../market/migrations/0001_initial.py | 188 ++++++++++++++++++ .../market/migrations/__init__.py | 0 polylan_submitter/market/models.py | 88 ++++++++ polylan_submitter/market/tests.py | 3 + polylan_submitter/market/views.py | 3 + .../polylan_submitter/settings.py | 1 + 9 files changed, 292 insertions(+) create mode 100644 polylan_submitter/market/__init__.py create mode 100644 polylan_submitter/market/admin.py create mode 100644 polylan_submitter/market/apps.py create mode 100644 polylan_submitter/market/migrations/0001_initial.py create mode 100644 polylan_submitter/market/migrations/__init__.py create mode 100644 polylan_submitter/market/models.py create mode 100644 polylan_submitter/market/tests.py create mode 100644 polylan_submitter/market/views.py diff --git a/polylan_submitter/market/__init__.py b/polylan_submitter/market/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/polylan_submitter/market/admin.py b/polylan_submitter/market/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/polylan_submitter/market/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/polylan_submitter/market/apps.py b/polylan_submitter/market/apps.py new file mode 100644 index 0000000..b41e2d7 --- /dev/null +++ b/polylan_submitter/market/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MarketConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "market" diff --git a/polylan_submitter/market/migrations/0001_initial.py b/polylan_submitter/market/migrations/0001_initial.py new file mode 100644 index 0000000..3ffd3a7 --- /dev/null +++ b/polylan_submitter/market/migrations/0001_initial.py @@ -0,0 +1,188 @@ +# Generated by Django 5.2.7 on 2026-05-23 15:45 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Market", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "uuid", + models.UUIDField(default=uuid.uuid4, editable=False, unique=True), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("title", models.CharField(max_length=255)), + ("description", models.TextField(blank=True)), + ( + "type", + models.CharField( + choices=[("yes_no", "Yes/No"), ("multiple", "Multiple Choice")], + default="yes_no", + max_length=10, + ), + ), + ( + "status", + models.CharField( + choices=[ + ("open", "Open"), + ("closed", "Closed"), + ("resolved", "Resolved"), + ], + default="open", + max_length=10, + ), + ), + ("end_date", models.DateTimeField()), + ( + "created_by", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_at"], + }, + ), + migrations.CreateModel( + name="MarketOption", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "uuid", + models.UUIDField(default=uuid.uuid4, editable=False, unique=True), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("text", models.CharField(max_length=255)), + ("position", models.PositiveIntegerField(default=0)), + ( + "market", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="options", + to="market.market", + ), + ), + ], + options={ + "ordering": ["position"], + }, + ), + migrations.AddField( + model_name="market", + name="winning_option", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="market_won", + to="market.marketoption", + ), + ), + migrations.CreateModel( + name="UserBet", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "uuid", + models.UUIDField(default=uuid.uuid4, editable=False, unique=True), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("amount", models.PositiveIntegerField()), + ( + "option", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="user_bets", + to="market.marketoption", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="bets", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.AddIndex( + model_name="marketoption", + index=models.Index( + fields=["market", "position"], name="market_mark_market__8679ce_idx" + ), + ), + migrations.AddConstraint( + model_name="marketoption", + constraint=models.UniqueConstraint( + fields=("market", "position"), name="unique_market_option_position" + ), + ), + migrations.AddIndex( + model_name="market", + index=models.Index( + fields=["status", "-created_at"], name="market_mark_status_1ef6c3_idx" + ), + ), + migrations.AddIndex( + model_name="market", + index=models.Index( + fields=["end_date"], name="market_mark_end_dat_26bec0_idx" + ), + ), + migrations.AddIndex( + model_name="userbet", + index=models.Index( + fields=["user", "option"], name="market_user_user_id_5e43d9_idx" + ), + ), + migrations.AddConstraint( + model_name="userbet", + constraint=models.UniqueConstraint( + fields=("user", "option"), name="unique_user_bet_per_option" + ), + ), + ] diff --git a/polylan_submitter/market/migrations/__init__.py b/polylan_submitter/market/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/polylan_submitter/market/models.py b/polylan_submitter/market/models.py new file mode 100644 index 0000000..2a4a9a5 --- /dev/null +++ b/polylan_submitter/market/models.py @@ -0,0 +1,88 @@ +import uuid +from django.db import models +from django.utils import timezone + + +class BaseModel(models.Model): + uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + + +class Market(BaseModel): + class Type(models.TextChoices): + YES_NO = "yes_no", "Yes/No" + MULTIPLE = "multiple", "Multiple Choice" + + class Status(models.TextChoices): + OPEN = "open", "Open" + CLOSED = "closed", "Closed" + RESOLVED = "resolved", "Resolved" + + title = models.CharField(max_length=255) + description = models.TextField(blank=True) + type = models.CharField(max_length=10, choices=Type.choices, default=Type.YES_NO) + status = models.CharField(max_length=10, choices=Status.choices, default=Status.OPEN) + end_date = models.DateTimeField() + created_by = models.ForeignKey("accounts.CustomUser", on_delete=models.PROTECT) + winning_option = models.ForeignKey( + "MarketOption", + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name="market_won", + ) + + class Meta: + ordering = ["-created_at"] + indexes = [ + models.Index(fields=["status", "-created_at"]), + models.Index(fields=["end_date"]), + ] + + def __str__(self): + return self.title + + +class MarketOption(BaseModel): + market = models.ForeignKey(Market, on_delete=models.CASCADE, related_name="options") + text = models.CharField(max_length=255) + position = models.PositiveIntegerField(default=0) + + class Meta: + ordering = ["position"] + constraints = [ + models.UniqueConstraint( + fields=["market", "position"], + name="unique_market_option_position", + ), + ] + indexes = [ + models.Index(fields=["market", "position"]), + ] + + def __str__(self): + return f"{self.market.title} - {self.text}" + + +class UserBet(BaseModel): + user = models.ForeignKey("accounts.CustomUser", on_delete=models.CASCADE, related_name="bets") + option = models.ForeignKey(MarketOption, on_delete=models.CASCADE, related_name="user_bets") + amount = models.PositiveIntegerField() + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["user", "option"], + name="unique_user_bet_per_option", + ), + ] + indexes = [ + models.Index(fields=["user", "option"]), + ] + + def __str__(self): + return f"{self.user.username} bet {self.amount} on {self.option.text}" diff --git a/polylan_submitter/market/tests.py b/polylan_submitter/market/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/polylan_submitter/market/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/polylan_submitter/market/views.py b/polylan_submitter/market/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/polylan_submitter/market/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/polylan_submitter/polylan_submitter/settings.py b/polylan_submitter/polylan_submitter/settings.py index b6f02f1..e26c1fb 100644 --- a/polylan_submitter/polylan_submitter/settings.py +++ b/polylan_submitter/polylan_submitter/settings.py @@ -44,6 +44,7 @@ INSTALLED_APPS = [ "submissions", "noita", "games", + "market", ] MIDDLEWARE = [