models.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import logging
  2. from datetime import datetime
  3. from typing import Optional
  4. from uuid import uuid4
  5. import requests
  6. from boardgames.bgg import lookup_boardgame_from_bgg
  7. from django.conf import settings
  8. from django.core.files.base import ContentFile
  9. from django.db import models
  10. from django.urls import reverse
  11. from django_extensions.db.models import TimeStampedModel
  12. from scrobbles.mixins import ScrobblableMixin
  13. logger = logging.getLogger(__name__)
  14. BNULL = {"blank": True, "null": True}
  15. class BoardGamePublisher(TimeStampedModel):
  16. name = models.CharField(max_length=255)
  17. uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
  18. logo = models.ImageField(upload_to="games/platform-logos/", **BNULL)
  19. igdb_id = models.IntegerField(**BNULL)
  20. def __str__(self):
  21. return self.name
  22. def get_absolute_url(self):
  23. return reverse(
  24. "boardgames:publisher_detail", kwargs={"slug": self.uuid}
  25. )
  26. class BoardGame(ScrobblableMixin):
  27. COMPLETION_PERCENT = getattr(
  28. settings, "BOARD_GAME_COMPLETION_PERCENT", 100
  29. )
  30. FIELDS_FROM_BGGEEK = [
  31. "igdb_id",
  32. "alternative_name",
  33. "rating",
  34. "rating_count",
  35. "release_date",
  36. "cover",
  37. "screenshot",
  38. ]
  39. title = models.CharField(max_length=255)
  40. publisher = models.ForeignKey(
  41. BoardGamePublisher, **BNULL, on_delete=models.DO_NOTHING
  42. )
  43. uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
  44. description = models.TextField(**BNULL)
  45. cover = models.ImageField(upload_to="boardgames/covers/", **BNULL)
  46. layout_image = models.ImageField(upload_to="boardgames/layouts/", **BNULL)
  47. rating = models.FloatField(**BNULL)
  48. max_players = models.PositiveSmallIntegerField(**BNULL)
  49. min_players = models.PositiveSmallIntegerField(**BNULL)
  50. published_date = models.DateField(**BNULL)
  51. recommended_age = models.PositiveSmallIntegerField(**BNULL)
  52. bggeek_id = models.CharField(max_length=255, **BNULL)
  53. def __str__(self):
  54. return self.title
  55. def get_absolute_url(self):
  56. return reverse(
  57. "boardgames:boardgame_detail", kwargs={"slug": self.uuid}
  58. )
  59. def primary_image_url(self) -> str:
  60. url = ""
  61. if self.cover:
  62. url = self.cover.url
  63. return url
  64. def bggeek_link(self):
  65. link = ""
  66. if self.bggeek_id:
  67. link = f"https://boardgamegeek.com/boardgame/{self.bggeek_id}"
  68. return link
  69. def fix_metadata(self, data: dict = {}, force_update=False) -> None:
  70. if not self.bggeek_id or force_update:
  71. if not data:
  72. data = get_game_by_id_from_bgg(self.bggeek_id)
  73. cover_url = data.pop("cover_url")
  74. year = data.pop("year_published")
  75. publisher_name = data.pop("publisher_name")
  76. if year:
  77. data["published_date"] = datetime(int(year), 1, 1)
  78. # Fun trick for updating all fields at once
  79. BoardGame.objects.filter(pk=self.id).update(**data)
  80. self.refresh_from_db()
  81. # Add publishers
  82. (
  83. self.publisher,
  84. _created,
  85. ) = BoardGamePublisher.objects.get_or_create(name=publisher_name)
  86. self.save()
  87. # Go get cover image if the URL is present
  88. if cover_url and not self.cover:
  89. headers = {"User-Agent": "Vrobbler 0.11.12"}
  90. r = requests.get(cover_url, headers=headers)
  91. logger.debug(r.status_code)
  92. if r.status_code == 200:
  93. fname = f"{self.title}_cover_{self.uuid}.jpg"
  94. self.cover.save(fname, ContentFile(r.content), save=True)
  95. logger.debug("Loaded cover image from BGGeek")
  96. @classmethod
  97. def find_or_create(
  98. cls, lookup_id: str, data: Optional[dict] = {}
  99. ) -> Optional["BoardGame"]:
  100. """Given a Lookup ID (either BGG or BGA ID), return a board game object"""
  101. boardgame = None
  102. if not data:
  103. data = lookup_boardgame_from_bgg(lookup_id)
  104. if data:
  105. boardgame, created = cls.objects.get_or_create(
  106. title=data["title"], bggeek_id=lookup_id
  107. )
  108. if created:
  109. boardgame.fix_metadata(data=data)
  110. return boardgame