|
@@ -0,0 +1,108 @@
|
|
|
+from uuid import uuid4
|
|
|
+
|
|
|
+from puzzles.sources import ipdb
|
|
|
+from django.apps import apps
|
|
|
+from django.db import models
|
|
|
+from django.urls import reverse
|
|
|
+from django_extensions.db.models import TimeStampedModel
|
|
|
+from imagekit.models import ImageSpecField
|
|
|
+from imagekit.processors import ResizeToFit
|
|
|
+from scrobbles.dataclasses import PuzzleLogData
|
|
|
+from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
|
|
|
+
|
|
|
+BNULL = {"blank": True, "null": True}
|
|
|
+
|
|
|
+
|
|
|
+class PuzzleManufacturer(TimeStampedModel):
|
|
|
+ uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
|
|
+ name = models.CharField(max_length=255)
|
|
|
+ ipdb_id = models.CharField(max_length=200, **BNULL)
|
|
|
+ description = models.TextField(**BNULL)
|
|
|
+
|
|
|
+ def __str__(self) -> str:
|
|
|
+ return str(self.name)
|
|
|
+
|
|
|
+
|
|
|
+class Puzzle(ScrobblableMixin):
|
|
|
+ description = models.TextField(**BNULL)
|
|
|
+ orientation = models.CharField(max_length=50, **BNULL)
|
|
|
+ dimensions_in_inches = models.CharField(max_length=30, **BNULL)
|
|
|
+ publish_year = models.IntegerField(**BNULL)
|
|
|
+ material = models.CharField(max_length=50, **BNULL)
|
|
|
+ cut_style = models.CharField(max_length=100, **BNULL)
|
|
|
+ pieces_count = models.IntegerField(**BNULL)
|
|
|
+ igdb_id = models.CharField(max_length=255, **BNULL)
|
|
|
+ barcode = models.CharField(max_length=13, **BNULL)
|
|
|
+ igdb_image = models.ImageField(upload_to="puzzles/igdb/", **BNULL)
|
|
|
+ igdb_image_small = ImageSpecField(
|
|
|
+ source="igdb_image",
|
|
|
+ processors=[ResizeToFit(100, 100)],
|
|
|
+ format="JPEG",
|
|
|
+ options={"quality": 60},
|
|
|
+ )
|
|
|
+ igdb_image_medium = ImageSpecField(
|
|
|
+ source="igdb_image",
|
|
|
+ processors=[ResizeToFit(300, 300)],
|
|
|
+ format="JPEG",
|
|
|
+ options={"quality": 75},
|
|
|
+ )
|
|
|
+ manufacturer = models.ForeignKey(
|
|
|
+ PuzzleManufacturer, on_delete=models.DO_NOTHING, **BNULL
|
|
|
+ )
|
|
|
+
|
|
|
+ def get_absolute_url(self) -> str:
|
|
|
+ return reverse("puzzles:puzzle_detail", kwargs={"slug": self.uuid})
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ return f"{self.title} ({self.pieces_count}) by {self.manufacturer}"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def subtitle(self):
|
|
|
+ return self.manufacturer.name
|
|
|
+
|
|
|
+ @property
|
|
|
+ def strings(self) -> ScrobblableConstants:
|
|
|
+ return ScrobblableConstants(verb="Solving", tags="puzzle")
|
|
|
+
|
|
|
+ @property
|
|
|
+ def igdb_link(self) -> str:
|
|
|
+ link = ""
|
|
|
+ if self.igdb_id:
|
|
|
+ link = f"https://www.ipdb.plus/IPDb/puzzle.php?id={self.igdb_id}"
|
|
|
+ return link
|
|
|
+
|
|
|
+ @property
|
|
|
+ def primary_image_url(self) -> str:
|
|
|
+ url = ""
|
|
|
+ if self.ipdb_image:
|
|
|
+ url = self.ipdb_image.url
|
|
|
+ return url
|
|
|
+
|
|
|
+ @property
|
|
|
+ def logdata_cls(self):
|
|
|
+ return PuzzleLogData
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def find_or_create(cls, ipdb_id: str) -> "Puzzle":
|
|
|
+ puzzle = cls.objects.filter(ipdb_id=ipdb_id).first()
|
|
|
+
|
|
|
+ if not puzzle:
|
|
|
+ puzzle_dict = ipdb.get_puzzle_from_ipdb_id(ipdb_id)
|
|
|
+ manufacturer_name = puzzle_dict.pop("manufacturer")
|
|
|
+ manufacturer, _created = PuzzleManufacturer.objects.get_or_create(
|
|
|
+ name=manufacturer_name
|
|
|
+ )
|
|
|
+ ipdb_dict["manufacturer_id"] = manufacturer.id
|
|
|
+
|
|
|
+ genres = puzzle_dict.pop("genres")
|
|
|
+ puzzle = Puzzle.objects.create(**puzzle_dict)
|
|
|
+ if genres:
|
|
|
+ puzzle.genre.add(*genres)
|
|
|
+
|
|
|
+ return puzzle
|
|
|
+
|
|
|
+ def scrobbles(self, user_id):
|
|
|
+ Scrobble = apps.get_model("scrobbles", "Scrobble")
|
|
|
+ return Scrobble.objects.filter(user_id=user_id, puzzle=self).order_by(
|
|
|
+ "-timestamp"
|
|
|
+ )
|