mixins.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. from dataclasses import dataclass
  2. import logging
  3. from typing import Optional
  4. from uuid import uuid4
  5. from django.apps import apps
  6. from django.db import models
  7. from django.urls import reverse
  8. from django.utils import timezone
  9. from django_extensions.db.models import TimeStampedModel
  10. from scrobbles.utils import get_scrobbles_for_media
  11. from taggit.managers import TaggableManager
  12. from taggit.models import GenericTaggedItemBase, TagBase
  13. BNULL = {"blank": True, "null": True}
  14. logger = logging.getLogger(__name__)
  15. @dataclass
  16. class ScrobblableConstants:
  17. verb: str
  18. tags: str
  19. priority: str
  20. def __init__(
  21. self,
  22. verb: str = "Scrobbling",
  23. tags: str = "green_square",
  24. priority: str = "default",
  25. ):
  26. self.verb = verb
  27. self.tags = tags
  28. self.priority = priority
  29. class Genre(TagBase):
  30. source = models.CharField(max_length=255, **BNULL)
  31. class Meta:
  32. verbose_name = "Genre"
  33. verbose_name_plural = "Genres"
  34. class ObjectWithGenres(GenericTaggedItemBase):
  35. tag = models.ForeignKey(
  36. Genre,
  37. on_delete=models.CASCADE,
  38. related_name="%(app_label)s_%(class)s_items",
  39. **BNULL,
  40. )
  41. class ScrobblableMixin(TimeStampedModel):
  42. SECONDS_TO_STALE = 1600
  43. COMPLETION_PERCENT = 100
  44. uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
  45. title = models.CharField(max_length=255, **BNULL)
  46. run_time_seconds = models.IntegerField(default=900)
  47. run_time_ticks = models.PositiveBigIntegerField(**BNULL)
  48. genre = TaggableManager(through=ObjectWithGenres, blank=True)
  49. class Meta:
  50. abstract = True
  51. def scrobble_for_user(
  52. self,
  53. user_id,
  54. source: str = "Vrobbler",
  55. playback_position_seconds: int = 0,
  56. status: str = "started",
  57. log: Optional[dict] = None,
  58. ):
  59. Scrobble = apps.get_model("scrobbles", "Scrobble")
  60. scrobble_data = {
  61. "user_id": user_id,
  62. "timestamp": timezone.now(),
  63. "source": source,
  64. "status": status,
  65. "playback_position_seconds": playback_position_seconds,
  66. }
  67. if log:
  68. scrobble_data["log"] = log
  69. logger.info(
  70. "[scrobble_for_user] called",
  71. extra={
  72. "id": self.id,
  73. "media_type": self.__class__,
  74. "user_id": user_id,
  75. "scrobble_data": scrobble_data,
  76. "media_type": Scrobble.MediaType.WEBPAGE,
  77. },
  78. )
  79. return Scrobble.create_or_update(self, user_id, scrobble_data)
  80. @property
  81. def start_url(self):
  82. return reverse("scrobbles:start", kwargs={"uuid": self.uuid})
  83. @property
  84. def strings(self) -> ScrobblableConstants:
  85. return ScrobblableConstants()
  86. @property
  87. def primary_image_url(self) -> str:
  88. logger.warning(f"Not implemented yet")
  89. return ""
  90. @property
  91. def logdata_cls(self) -> None:
  92. from scrobbles.dataclasses import ScrobbleLogData
  93. return ScrobbleLogData
  94. @property
  95. def subtitle(self) -> str:
  96. return ""
  97. def fix_metadata(self) -> None:
  98. logger.warning("fix_metadata() not implemented yet")
  99. @classmethod
  100. def find_or_create(cls):
  101. logger.warning("find_or_create() not implemented yet")
  102. def __str__(self) -> str:
  103. if self.title:
  104. return str(self.title)
  105. return str(self.uuid)
  106. class LongPlayScrobblableMixin(ScrobblableMixin):
  107. class Meta:
  108. abstract = True
  109. def get_longplay_finish_url(self):
  110. return reverse("scrobbles:longplay-finish", kwargs={"uuid": self.uuid})
  111. def first_long_play_scrobble_for_user(self, user) -> Optional["Scrobble"]:
  112. return (
  113. get_scrobbles_for_media(self, user)
  114. .filter(
  115. log__long_play_complete=False,
  116. log__serial_scrobble_id__isnull=True,
  117. )
  118. .order_by("timestamp")
  119. .first()
  120. )
  121. def last_long_play_scrobble_for_user(self, user) -> Optional["Scrobble"]:
  122. return (
  123. get_scrobbles_for_media(self, user)
  124. .filter(
  125. log__long_play_complete=False,
  126. log__serial_scrobble_id__isnull=False,
  127. )
  128. .order_by("timestamp")
  129. .last()
  130. )