.
This commit is contained in:
+137
-5
@@ -19,12 +19,24 @@ class Dinosaur(models.Model):
|
||||
diet = models.CharField(max_length=32, choices=DIET_CHOICES, default='herbivore')
|
||||
description = models.TextField(blank=True)
|
||||
image = models.ImageField(upload_to='dinosaurs/', blank=True, null=True)
|
||||
background = models.ForeignKey(
|
||||
'cards.BackgroundImage',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='dinosaurs',
|
||||
help_text='Optional per-card background image'
|
||||
)
|
||||
|
||||
# Top-trump style stats (0-100)
|
||||
speed = models.PositiveSmallIntegerField(default=50)
|
||||
weight = models.PositiveSmallIntegerField(default=50)
|
||||
intelligence = models.PositiveSmallIntegerField(default=50)
|
||||
height = models.PositiveSmallIntegerField(default=50)
|
||||
# Stats stored in real-world units now:
|
||||
# - `speed` in km/h
|
||||
# - `weight` in kg
|
||||
# - `height` in metres
|
||||
# - `intelligence` remains a 0-100 score
|
||||
speed = models.PositiveIntegerField(default=30, help_text='Speed in km/h')
|
||||
weight = models.PositiveIntegerField(default=1000, help_text='Weight in kg')
|
||||
intelligence = models.PositiveSmallIntegerField(default=50, help_text='Score 0-100')
|
||||
height = models.DecimalField(max_digits=5, decimal_places=1, default=1.5, help_text='Height in metres')
|
||||
facts = models.TextField(blank=True, help_text='One fact per line; shown as bullet points on the card')
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
@@ -42,6 +54,126 @@ class Dinosaur(models.Model):
|
||||
return []
|
||||
return [f.strip() for f in self.facts.splitlines() if f.strip()]
|
||||
|
||||
# -- Unit helpers / display properties -------------------------------------------------
|
||||
# These map the 0-100 top-trump style values to human-friendly units. The numeric
|
||||
# mappings are conservative defaults and can be adjusted if you want different ranges.
|
||||
# Ranges used to convert between stored unit values and the 0-100 bar/star scale.
|
||||
SPEED_MAX_KMH = 60.0
|
||||
WEIGHT_MAX_KG = 15000.0
|
||||
HEIGHT_MAX_M = 12.0
|
||||
|
||||
# Display helpers (use stored unit values directly)
|
||||
@property
|
||||
def speed_display(self):
|
||||
return f"{self.speed} km/h"
|
||||
|
||||
@property
|
||||
def weight_display(self):
|
||||
kg = int(self.weight)
|
||||
if kg >= 1000:
|
||||
tonnes = round(kg / 1000.0, 1)
|
||||
return f"{tonnes} t"
|
||||
return f"{kg} kg"
|
||||
|
||||
@property
|
||||
def height_display(self):
|
||||
# height is a DecimalField; format to one decimal place
|
||||
return f"{float(self.height):.1f} m"
|
||||
|
||||
@property
|
||||
def intelligence_display(self):
|
||||
return f"{self.intelligence}%"
|
||||
|
||||
# Percent helpers: convert stored units back to a 0-100 scale for bars/stars
|
||||
def speed_percent(self):
|
||||
return min(100, int(round(self.speed / float(self.SPEED_MAX_KMH) * 100)))
|
||||
|
||||
def weight_percent(self):
|
||||
return min(100, int(round(self.weight / float(self.WEIGHT_MAX_KG) * 100)))
|
||||
|
||||
def height_percent(self):
|
||||
return min(100, int(round(float(self.height) / float(self.HEIGHT_MAX_M) * 100)))
|
||||
|
||||
def intelligence_percent(self):
|
||||
return min(100, int(self.intelligence or 0))
|
||||
|
||||
# -- Star rating helpers -------------------------------------------------------------
|
||||
def _star_rating_from_percent(self, percent):
|
||||
"""Convert a 0-100 percent into a 0-5 star score in 0.5 increments."""
|
||||
if percent is None:
|
||||
return 0.0
|
||||
val = float(percent) * 0.05 # scale 0-100 -> 0-5
|
||||
# round to nearest 0.5
|
||||
return round(val * 2) / 2.0
|
||||
|
||||
def _stars_string(self, percent):
|
||||
"""Return a simple star string like '★★★★½' and trailing empty stars '☆'."""
|
||||
stars = self._star_rating_from_percent(percent)
|
||||
full = int(stars)
|
||||
half = 1 if (stars - full) >= 0.5 else 0
|
||||
empty = 5 - full - half
|
||||
return "★" * full + ("½" if half else "") + "☆" * empty
|
||||
|
||||
def _star_types_from_percent(self, percent):
|
||||
"""Return a list of five items: 'full', 'half', or 'empty'."""
|
||||
stars = self._star_rating_from_percent(percent)
|
||||
full = int(stars)
|
||||
half = 1 if (stars - full) >= 0.5 else 0
|
||||
types = ['full'] * full
|
||||
if half:
|
||||
types.append('half')
|
||||
types.extend(['empty'] * (5 - len(types)))
|
||||
return types
|
||||
|
||||
# Expose star strings and original values for templates
|
||||
@property
|
||||
def speed_stars(self):
|
||||
return self._stars_string(self.speed_percent())
|
||||
|
||||
@property
|
||||
def speed_star_types(self):
|
||||
return self._star_types_from_percent(self.speed_percent())
|
||||
|
||||
@property
|
||||
def speed_orig(self):
|
||||
return self.speed
|
||||
|
||||
@property
|
||||
def weight_stars(self):
|
||||
return self._stars_string(self.weight_percent())
|
||||
|
||||
@property
|
||||
def weight_star_types(self):
|
||||
return self._star_types_from_percent(self.weight_percent())
|
||||
|
||||
@property
|
||||
def weight_orig(self):
|
||||
return self.weight
|
||||
|
||||
@property
|
||||
def height_stars(self):
|
||||
return self._stars_string(self.height_percent())
|
||||
|
||||
@property
|
||||
def height_star_types(self):
|
||||
return self._star_types_from_percent(self.height_percent())
|
||||
|
||||
@property
|
||||
def height_orig(self):
|
||||
return self.height
|
||||
|
||||
@property
|
||||
def intelligence_stars(self):
|
||||
return self._stars_string(self.intelligence_percent())
|
||||
|
||||
@property
|
||||
def intelligence_star_types(self):
|
||||
return self._star_types_from_percent(self.intelligence_percent())
|
||||
|
||||
@property
|
||||
def intelligence_orig(self):
|
||||
return self.intelligence
|
||||
|
||||
|
||||
class BackgroundImage(models.Model):
|
||||
"""Background images used behind card renders.
|
||||
|
||||
Reference in New Issue
Block a user