models.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from dataclasses import dataclass
  2. from typing import Optional
  3. from uuid import uuid4
  4. import requests
  5. from django.apps import apps
  6. from django.core.files.base import ContentFile
  7. from django.db import models
  8. from django.urls import reverse
  9. from django_extensions.db.models import TimeStampedModel
  10. from imagekit.models import ImageSpecField
  11. from imagekit.processors import ResizeToFit
  12. from puzzles.sources import ipdb
  13. from scrobbles.constants import MediaType
  14. from scrobbles.dataclasses import BaseLogData, WithPeopleLogData, LongPlayLogData
  15. from scrobbles.mixins import ScrobblableConstants, ScrobblableItem, ObjectWithGenres
  16. from taggit.managers import TaggableManager
  17. BNULL = {"blank": True, "null": True}
  18. @dataclass
  19. class PuzzleLogData(BaseLogData, WithPeopleLogData, LongPlayLogData):
  20. rating: Optional[str] = None
  21. class PuzzleManufacturer(TimeStampedModel):
  22. uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
  23. name = models.CharField(max_length=255)
  24. ipdb_id = models.CharField(max_length=200, **BNULL)
  25. description = models.TextField(**BNULL)
  26. def __str__(self) -> str:
  27. return str(self.name)
  28. class Puzzle(ScrobblableItem):
  29. MEDIA_TYPE = MediaType.PUZZLE
  30. scrobblableitem_ptr = models.OneToOneField(
  31. ScrobblableItem,
  32. parent_link=True,
  33. primary_key=True,
  34. on_delete=models.CASCADE,
  35. related_name="as_puzzle",
  36. )
  37. orientation = models.CharField(max_length=50, **BNULL)
  38. dimensions_in_inches = models.CharField(max_length=30, **BNULL)
  39. publish_year = models.IntegerField(**BNULL)
  40. material = models.CharField(max_length=50, **BNULL)
  41. cut_style = models.CharField(max_length=100, **BNULL)
  42. pieces_count = models.IntegerField(**BNULL)
  43. ipdb_id = models.CharField(max_length=255, **BNULL)
  44. barcode = models.CharField(max_length=13, **BNULL)
  45. ipdb_image = models.ImageField(upload_to="puzzles/ipdb/", **BNULL)
  46. ipdb_image_small = ImageSpecField(
  47. source="ipdb_image",
  48. processors=[ResizeToFit(100, 100)],
  49. format="JPEG",
  50. options={"quality": 60},
  51. )
  52. ipdb_image_medium = ImageSpecField(
  53. source="ipdb_image",
  54. processors=[ResizeToFit(300, 300)],
  55. format="JPEG",
  56. options={"quality": 75},
  57. )
  58. manufacturer = models.ForeignKey(
  59. PuzzleManufacturer, on_delete=models.DO_NOTHING, **BNULL
  60. )
  61. genre = TaggableManager(through=ObjectWithGenres)
  62. def get_absolute_url(self) -> str:
  63. return reverse("puzzles:puzzle_detail", kwargs={"slug": self.uuid})
  64. def __str__(self):
  65. return f"{self.title} ({self.pieces_count}) by {self.manufacturer}"
  66. @property
  67. def logdata_cls(self):
  68. return PuzzleLogData
  69. @property
  70. def subtitle(self):
  71. return self.manufacturer.name
  72. @property
  73. def strings(self) -> ScrobblableConstants:
  74. return ScrobblableConstants(verb="Solving", tags="puzzle")
  75. @property
  76. def ipdb_link(self) -> str:
  77. link = ""
  78. if self.ipdb_id:
  79. link = f"https://www.ipdb.plus/IPDb/puzzle.php?id={self.ipdb_id}"
  80. return link
  81. @property
  82. def primary_image_url(self) -> str:
  83. url = ""
  84. if self.ipdb_image:
  85. url = self.ipdb_image.url
  86. return url
  87. @property
  88. def logdata_cls(self):
  89. return PuzzleLogData
  90. @classmethod
  91. def find_or_create(cls, ipdb_id: str) -> "Puzzle":
  92. puzzle = cls.objects.filter(ipdb_id=ipdb_id).first()
  93. if not puzzle:
  94. puzzle_dict = ipdb.get_puzzle_from_ipdb_id(ipdb_id)
  95. manufacturer_name = puzzle_dict.pop("manufacturer", None)
  96. if manufacturer_name:
  97. (
  98. manufacturer,
  99. _created,
  100. ) = PuzzleManufacturer.objects.get_or_create(
  101. name=manufacturer_name
  102. )
  103. puzzle_dict["manufacturer_id"] = manufacturer.id
  104. genres = puzzle_dict.pop("genres", None)
  105. cover_url = puzzle_dict.pop("ipdb_image_url", None)
  106. puzzle = Puzzle.objects.create(**puzzle_dict)
  107. if genres:
  108. puzzle.genre.add(*genres)
  109. if cover_url:
  110. r = requests.get(cover_url)
  111. if r.status_code == 200:
  112. fname = f"{puzzle.title}_{puzzle.uuid}.jpg"
  113. puzzle.ipdb_image.save(
  114. fname, ContentFile(r.content), save=True
  115. )
  116. return puzzle
  117. def scrobbles(self, user_id):
  118. Scrobble = apps.get_model("scrobbles", "Scrobble")
  119. return Scrobble.objects.filter(user_id=user_id, puzzle=self).order_by(
  120. "-timestamp"
  121. )