import calendar from django.contrib.auth import get_user_model from django.db import models from django.urls import reverse from django_extensions.db.models import TimeStampedModel User = get_user_model() BNULL = {"blank": True, "null": True} class ChartRecord(TimeStampedModel): """Stores rankings for a user/media/time period combination. Example entries: - Bjork was #3 artist in 2022 - Homogenic was #10 album in April 2025 - The Office was #1 TV series in Week #34 2024 """ user = models.ForeignKey(User, on_delete=models.CASCADE, **BNULL) year = models.IntegerField() month = models.IntegerField(**BNULL) week = models.IntegerField(**BNULL) day = models.IntegerField(**BNULL) artist = models.ForeignKey("music.Artist", on_delete=models.CASCADE, **BNULL) album = models.ForeignKey("music.Album", on_delete=models.CASCADE, **BNULL) track = models.ForeignKey("music.Track", on_delete=models.CASCADE, **BNULL) tv_series = models.ForeignKey("videos.Series", on_delete=models.CASCADE, **BNULL) video = models.ForeignKey("videos.Video", on_delete=models.CASCADE, **BNULL) podcast = models.ForeignKey("podcasts.Podcast", on_delete=models.CASCADE, **BNULL) podcast_episode = models.ForeignKey( "podcasts.PodcastEpisode", on_delete=models.CASCADE, **BNULL ) board_game = models.ForeignKey( "boardgames.BoardGame", on_delete=models.CASCADE, **BNULL ) trail = models.ForeignKey("trails.Trail", on_delete=models.CASCADE, **BNULL) geo_location = models.ForeignKey( "locations.GeoLocation", on_delete=models.CASCADE, **BNULL ) food = models.ForeignKey("foods.Food", on_delete=models.CASCADE, **BNULL) book = models.ForeignKey("books.Book", on_delete=models.CASCADE, **BNULL) rank = models.IntegerField() count = models.IntegerField(default=0) class Meta: indexes = [ models.Index(fields=["user", "year", "artist", "rank"]), models.Index(fields=["user", "year", "album", "rank"]), models.Index(fields=["user", "year", "track", "rank"]), models.Index(fields=["user", "year", "tv_series", "rank"]), models.Index(fields=["user", "year", "video", "rank"]), models.Index(fields=["user", "year", "podcast", "rank"]), models.Index(fields=["user", "year", "board_game", "rank"]), models.Index(fields=["user", "year", "trail", "rank"]), models.Index(fields=["user", "year", "geo_location", "rank"]), models.Index(fields=["user", "year", "food", "rank"]), models.Index(fields=["user", "year", "book", "rank"]), models.Index(fields=["user", "year", "week", "artist", "rank"]), models.Index(fields=["user", "year", "week", "tv_series", "rank"]), models.Index(fields=["user", "year", "month", "artist", "rank"]), models.Index(fields=["user", "year", "month", "tv_series", "rank"]), ] @property def media_obj(self): for attr in [ "artist", "album", "track", "tv_series", "video", "podcast", "podcast_episode", "board_game", "trail", "geo_location", "food", "book", ]: obj = getattr(self, attr) if obj: return obj return None @property def media_type(self): mapping = { "artist": "Artist", "album": "Album", "track": "Track", "tv_series": "TV Series", "video": "Video", "podcast": "Podcast", "podcast_episode": "Podcast Episode", "board_game": "Board Game", "trail": "Trail", "geo_location": "Location", "food": "Food", "book": "Book", } for attr, name in mapping.items(): if getattr(self, attr) is not None: return name return None @property def period_str(self) -> str: period = str(self.year) if self.month: period = f"{calendar.month_name[self.month]} {period}" if self.week: period = f"Week {self.week}, {period}" if self.day: period = f"{calendar.month_name[self.month]} {self.day}, {period}" return period @property def period_type(self) -> str: if self.day: return "day" if self.week: return "week" if self.month: return "month" return "year" def __str__(self): obj = self.media_obj obj_name = str(obj) if obj else "Unknown" return f"#{self.rank} {obj_name} in {self.period_str}" def get_absolute_url(self): return reverse("charts:charts-home") @property def chart_url(self) -> str: url = reverse("charts:charts-home") date_str = str(self.year) if self.week: date_str = f"{self.year}-W{self.week}" elif self.month: date_str = f"{self.year}-{self.month:02d}" return f"{url}?date={date_str}"