mutiple fixes
This commit is contained in:
@@ -1,11 +1,43 @@
|
||||
from typing import Self
|
||||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import ValidationError
|
||||
import uuid
|
||||
|
||||
from django.db.models.expressions import Window
|
||||
from django.db.models.functions import Cast, Rank, RowNumber
|
||||
from django.db.models.query import F
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class JsonIndex(models.Func):
|
||||
function = ""
|
||||
template = "%(json_field)s -> (%(index)s::int)"
|
||||
|
||||
def __init__(self, json_field, index_expression, **extra):
|
||||
super().__init__(json_field, index_expression, **extra)
|
||||
self.output_field = models.IntegerField()
|
||||
|
||||
def resolve_expression(
|
||||
self, query, allow_joins=True, reuse=None, summarize=False, for_save=False
|
||||
):
|
||||
# Resolve both expressions in the query context to ensure joins are set up correctly
|
||||
clone = self.copy()
|
||||
clone.source_expressions = [
|
||||
expr.resolve_expression(query, allow_joins, reuse, summarize, for_save)
|
||||
for expr in self.source_expressions
|
||||
]
|
||||
return clone
|
||||
|
||||
def as_sql(self, compiler, connection):
|
||||
json_sql, json_params = compiler.compile(self.source_expressions[0])
|
||||
idx_sql, idx_params = compiler.compile(self.source_expressions[1])
|
||||
sql = self.template % {"json_field": json_sql, "index": idx_sql}
|
||||
params = json_params + idx_params
|
||||
return sql, params
|
||||
|
||||
|
||||
class SteamAPIKey(models.Model):
|
||||
"""Model to store Steam API key configuration - Admin only"""
|
||||
|
||||
@@ -178,6 +210,19 @@ class SteamCollectionItem(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
# Puzzle points
|
||||
points_factor = models.ForeignKey(
|
||||
"animations.PuzzlePointsFactor",
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
points_value = models.ForeignKey(
|
||||
"animations.PuzzlePointsValue",
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["collection", "order_index"]
|
||||
unique_together = ["collection", "steam_item_id"]
|
||||
@@ -271,6 +316,48 @@ class Submission(models.Model):
|
||||
return self.responses.filter(needs_manual_validation=True).exists()
|
||||
|
||||
|
||||
class PuzzleResponseQuerySet(models.QuerySet):
|
||||
def annotate_rank_points(self) -> Self:
|
||||
return (
|
||||
self.annotate(
|
||||
points=F("puzzle__points_factor__cost") * F("validated_cost")
|
||||
+ F("puzzle__points_factor__cycles") * F("validated_cycles")
|
||||
+ F("puzzle__points_factor__area") * F("validated_area")
|
||||
)
|
||||
.annotate(
|
||||
user_response_rank=Window(
|
||||
expression=RowNumber(),
|
||||
partition_by=[F("puzzle"), F("submission__user")],
|
||||
order_by=F("points").asc(),
|
||||
)
|
||||
)
|
||||
# .filter(user_response_rank=1)
|
||||
.annotate(
|
||||
puzzle_user_rank=Window(
|
||||
expression=Rank(),
|
||||
partition_by=[F("puzzle")],
|
||||
order_by=F("points").asc(),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
rank_points=Cast(
|
||||
JsonIndex(
|
||||
F("puzzle__points_value__points"),
|
||||
Cast(F("puzzle_user_rank") - 1, models.IntegerField()),
|
||||
),
|
||||
models.IntegerField(),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def filter_user_best_response(self) -> Self:
|
||||
return self.annotate_rank_points().filter(user_response_rank=1)
|
||||
|
||||
|
||||
class PuzzleResponseManager(models.Manager.from_queryset(PuzzleResponseQuerySet)):
|
||||
pass
|
||||
|
||||
|
||||
class PuzzleResponse(models.Model):
|
||||
"""Model representing a response/solution for a specific puzzle"""
|
||||
|
||||
@@ -289,11 +376,9 @@ class PuzzleResponse(models.Model):
|
||||
puzzle_name = models.CharField(
|
||||
max_length=255, help_text="Puzzle name as detected by OCR"
|
||||
)
|
||||
cost = models.CharField(max_length=20, blank=True, help_text="Cost value from OCR")
|
||||
cycles = models.CharField(
|
||||
max_length=20, blank=True, help_text="Cycles value from OCR"
|
||||
)
|
||||
area = models.CharField(max_length=20, blank=True, help_text="Area value from OCR")
|
||||
cost = models.IntegerField(blank=True, help_text="Cost value from OCR")
|
||||
cycles = models.IntegerField(blank=True, help_text="Cycles value from OCR")
|
||||
area = models.IntegerField(blank=True, help_text="Area value from OCR")
|
||||
|
||||
# Validation flags
|
||||
needs_manual_validation = models.BooleanField(
|
||||
@@ -311,24 +396,27 @@ class PuzzleResponse(models.Model):
|
||||
)
|
||||
|
||||
# Manual validation overrides
|
||||
validated_cost = models.CharField(
|
||||
max_length=20, blank=True, help_text="Manually validated cost value"
|
||||
validated_cost = models.IntegerField(
|
||||
null=True, blank=True, help_text="Manually validated cost value"
|
||||
)
|
||||
validated_cycles = models.CharField(
|
||||
max_length=20, blank=True, help_text="Manually validated cycles value"
|
||||
validated_cycles = models.IntegerField(
|
||||
null=True, blank=True, help_text="Manually validated cycles value"
|
||||
)
|
||||
validated_area = models.CharField(
|
||||
max_length=20, blank=True, help_text="Manually validated area value"
|
||||
validated_area = models.IntegerField(
|
||||
null=True, blank=True, help_text="Manually validated area value"
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
objects = PuzzleResponseManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ["submission", "puzzle__order_index"]
|
||||
verbose_name = "Puzzle Response"
|
||||
verbose_name_plural = "Puzzle Responses"
|
||||
_base_manager_name = "objects"
|
||||
|
||||
def __str__(self):
|
||||
return f"Response for {self.puzzle_name} in {self.submission}"
|
||||
|
||||
Reference in New Issue
Block a user