diff --git a/vrobbler/apps/books/koreader.py b/vrobbler/apps/books/koreader.py index 41dd043..1058d8d 100644 --- a/vrobbler/apps/books/koreader.py +++ b/vrobbler/apps/books/koreader.py @@ -139,7 +139,6 @@ def build_book_map(rows) -> dict: book_id_map = {} for book_row in rows: - if book_row[KoReaderBookColumn.TITLE.value] in BOOKS_TITLES_TO_IGNORE: logger.info( "[build_book_map] Ignoring book title that is likely garbage", @@ -147,9 +146,7 @@ def build_book_map(rows) -> dict: ) continue book = Book.objects.filter( - koreader_data_by_hash__icontains=book_row[ - KoReaderBookColumn.MD5.value - ] + koreader_data_by_hash__icontains=book_row[KoReaderBookColumn.MD5.value] ).first() if not book: @@ -203,15 +200,11 @@ def build_page_data(page_rows: list, book_map: dict, user_tz=None) -> dict: "end_ts": start_ts + duration, } if book_ids_not_found: - logger.info( - f"Found pages for books not in file: {set(book_ids_not_found)}" - ) + logger.info(f"Found pages for books not in file: {set(book_ids_not_found)}") return book_map -def build_scrobbles_from_book_map( - book_map: dict, user: "User" -) -> list["Scrobble"]: +def build_scrobbles_from_book_map(book_map: dict, user: "User") -> list["Scrobble"]: Scrobble = apps.get_model("scrobbles", "Scrobble") scrobbles_to_create = [] @@ -241,9 +234,9 @@ def build_scrobbles_from_book_map( seconds_from_last_page = 0 if prev_page_stats: - seconds_from_last_page = stats.get( - "end_ts" - ) - prev_page_stats.get("start_ts") + seconds_from_last_page = stats.get("end_ts") - prev_page_stats.get( + "start_ts" + ) playback_position_seconds = playback_position_seconds + stats.get( "duration" @@ -252,9 +245,7 @@ def build_scrobbles_from_book_map( end_of_reading = pages_processed == total_pages_read big_jump_to_this_page = (cur_page_number - last_page_number) > 10 is_session_gap = seconds_from_last_page > SESSION_GAP_SECONDS - if ( - is_session_gap and not big_jump_to_this_page - ) or end_of_reading: + if (is_session_gap and not big_jump_to_this_page) or end_of_reading: should_create_scrobble = True if should_create_scrobble: @@ -286,9 +277,9 @@ def build_scrobbles_from_book_map( ) # Adjust for Daylight Saving Time - #if timestamp.dst() == timedelta( + # if timestamp.dst() == timedelta( # 0 - #) or stop_timestamp.dst() == timedelta(0): + # ) or stop_timestamp.dst() == timedelta(0): # timestamp = timestamp - timedelta(hours=1) # stop_timestamp = stop_timestamp - timedelta(hours=1) @@ -378,9 +369,7 @@ def process_koreader_sqlite_file(file_path, user_id) -> list: return new_scrobbles book_map = build_page_data( - cur.execute( - "SELECT * from page_stat_data ORDER BY id_book, start_time" - ), + cur.execute("SELECT * from page_stat_data ORDER BY id_book, start_time"), book_map, tz, ) diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index 4cc8be5..7915b7e 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -58,9 +58,7 @@ logger = logging.getLogger(__name__) User = get_user_model() BNULL = {"blank": True, "null": True} -POINTS_FOR_MOVEMENT_HISTORY = int( - getattr(settings, "POINTS_FOR_MOVEMENT_HISTORY", 3) -) +POINTS_FOR_MOVEMENT_HISTORY = int(getattr(settings, "POINTS_FOR_MOVEMENT_HISTORY", 3)) class BaseFileImportMixin(TimeStampedModel): @@ -96,7 +94,6 @@ class BaseFileImportMixin(TimeStampedModel): from scrobbles.models import Scrobble if not self.process_log: - logger.warning("No lines in process log found to undo") return @@ -104,9 +101,7 @@ class BaseFileImportMixin(TimeStampedModel): scrobble_id = line.split("\t")[0] scrobble = Scrobble.objects.filter(id=scrobble_id).first() if not scrobble: - logger.warning( - f"Could not find scrobble {scrobble_id} to undo" - ) + logger.warning(f"Could not find scrobble {scrobble_id} to undo") continue logger.info(f"Removing scrobble {scrobble_id}") if not dryrun: @@ -149,9 +144,7 @@ class BaseFileImportMixin(TimeStampedModel): return for count, scrobble in enumerate(scrobbles): - scrobble_str = ( - f"{scrobble.id}\t{scrobble.timestamp}\t{scrobble.media_obj}" - ) + scrobble_str = f"{scrobble.id}\t{scrobble.timestamp}\t{scrobble.media_obj}" log_line = f"{scrobble_str}" if count > 0: log_line = "\n" + log_line @@ -173,9 +166,7 @@ class KoReaderImport(BaseFileImportMixin): return "KOReader" def get_absolute_url(self): - return reverse( - "scrobbles:koreader-import-detail", kwargs={"slug": self.uuid} - ) + return reverse("scrobbles:koreader-import-detail", kwargs={"slug": self.uuid}) def get_path(instance, filename): extension = filename.split(".")[-1] @@ -211,15 +202,11 @@ class KoReaderImport(BaseFileImportMixin): fix_profile_historic_timezones(self.user.profile) if self.processed_finished and not force: - logger.info( - f"{self} already processed on {self.processed_finished}" - ) + logger.info(f"{self} already processed on {self.processed_finished}") return self.mark_started() - scrobbles = process_koreader_sqlite_file( - self.upload_file_path, self.user.id - ) + scrobbles = process_koreader_sqlite_file(self.upload_file_path, self.user.id) self.record_log(scrobbles) self.mark_finished() @@ -233,9 +220,7 @@ class AudioScrobblerTSVImport(BaseFileImportMixin): return "AudiosScrobbler" def get_absolute_url(self): - return reverse( - "scrobbles:tsv-import-detail", kwargs={"slug": self.uuid} - ) + return reverse("scrobbles:tsv-import-detail", kwargs={"slug": self.uuid}) def get_path(instance, filename): extension = filename.split(".")[-1] @@ -259,16 +244,12 @@ class AudioScrobblerTSVImport(BaseFileImportMixin): fix_profile_historic_timezones(self.user.profile) if self.processed_finished and not force: - logger.info( - f"{self} already processed on {self.processed_finished}" - ) + logger.info(f"{self} already processed on {self.processed_finished}") return self.mark_started() - scrobbles = import_audioscrobbler_tsv_file( - self.upload_file_path, self.user.id - ) + scrobbles = import_audioscrobbler_tsv_file(self.upload_file_path, self.user.id) self.record_log(scrobbles) self.mark_finished() @@ -282,9 +263,7 @@ class LastFmImport(BaseFileImportMixin): return "LastFM" def get_absolute_url(self): - return reverse( - "scrobbles:lastfm-import-detail", kwargs={"slug": self.uuid} - ) + return reverse("scrobbles:lastfm-import-detail", kwargs={"slug": self.uuid}) def process(self, import_all=False): """Import scrobbles found on LastFM""" @@ -293,9 +272,7 @@ class LastFmImport(BaseFileImportMixin): fix_profile_historic_timezones(self.user.profile) if self.processed_finished: - logger.info( - f"{self} already processed on {self.processed_finished}" - ) + logger.info(f"{self} already processed on {self.processed_finished}") return last_import = None @@ -333,9 +310,7 @@ class RetroarchImport(BaseFileImportMixin): return "Retroarch" def get_absolute_url(self): - return reverse( - "scrobbles:retroarch-import-detail", kwargs={"slug": self.uuid} - ) + return reverse("scrobbles:retroarch-import-detail", kwargs={"slug": self.uuid}) def process(self, import_all=False, force=False): """Import scrobbles found on Retroarch""" @@ -343,9 +318,7 @@ class RetroarchImport(BaseFileImportMixin): fix_profile_historic_timezones(self.user.profile) if self.processed_finished and not force: - logger.info( - f"{self} already processed on {self.processed_finished}" - ) + logger.info(f"{self} already processed on {self.processed_finished}") return if force: @@ -536,39 +509,25 @@ class Scrobble(TimeStampedModel): podcast_episode = models.ForeignKey( PodcastEpisode, on_delete=models.DO_NOTHING, **BNULL ) - sport_event = models.ForeignKey( - SportEvent, on_delete=models.DO_NOTHING, **BNULL - ) + sport_event = models.ForeignKey(SportEvent, on_delete=models.DO_NOTHING, **BNULL) book = models.ForeignKey(Book, on_delete=models.DO_NOTHING, **BNULL) paper = models.ForeignKey(Paper, on_delete=models.DO_NOTHING, **BNULL) - video_game = models.ForeignKey( - VideoGame, on_delete=models.DO_NOTHING, **BNULL - ) - board_game = models.ForeignKey( - BoardGame, on_delete=models.DO_NOTHING, **BNULL - ) - geo_location = models.ForeignKey( - GeoLocation, on_delete=models.DO_NOTHING, **BNULL - ) + video_game = models.ForeignKey(VideoGame, on_delete=models.DO_NOTHING, **BNULL) + board_game = models.ForeignKey(BoardGame, on_delete=models.DO_NOTHING, **BNULL) + geo_location = models.ForeignKey(GeoLocation, on_delete=models.DO_NOTHING, **BNULL) beer = models.ForeignKey(Beer, on_delete=models.DO_NOTHING, **BNULL) puzzle = models.ForeignKey(Puzzle, on_delete=models.DO_NOTHING, **BNULL) food = models.ForeignKey(Food, on_delete=models.DO_NOTHING, **BNULL) trail = models.ForeignKey(Trail, on_delete=models.DO_NOTHING, **BNULL) task = models.ForeignKey(Task, on_delete=models.DO_NOTHING, **BNULL) web_page = models.ForeignKey(WebPage, on_delete=models.DO_NOTHING, **BNULL) - life_event = models.ForeignKey( - LifeEvent, on_delete=models.DO_NOTHING, **BNULL - ) + life_event = models.ForeignKey(LifeEvent, on_delete=models.DO_NOTHING, **BNULL) mood = models.ForeignKey(Mood, on_delete=models.DO_NOTHING, **BNULL) - brick_set = models.ForeignKey( - BrickSet, on_delete=models.DO_NOTHING, **BNULL - ) + brick_set = models.ForeignKey(BrickSet, on_delete=models.DO_NOTHING, **BNULL) media_type = models.CharField( max_length=14, choices=MediaType.choices, default=MediaType.VIDEO ) - user = models.ForeignKey( - User, blank=True, null=True, on_delete=models.DO_NOTHING - ) + user = models.ForeignKey(User, blank=True, null=True, on_delete=models.DO_NOTHING) # Time keeping timestamp = models.DateTimeField(**BNULL) @@ -596,9 +555,7 @@ class Scrobble(TimeStampedModel): upload_to="scrobbles/videogame_save_data/", **BNULL ) gpx_file = models.FileField(upload_to="scrobbles/gpx_file/", **BNULL) - screenshot = models.ImageField( - upload_to="scrobbles/videogame_screenshot/", **BNULL - ) + screenshot = models.ImageField(upload_to="scrobbles/videogame_screenshot/", **BNULL) screenshot_small = ImageSpecField( source="screenshot", processors=[ResizeToFit(100, 100)], @@ -614,6 +571,16 @@ class Scrobble(TimeStampedModel): long_play_seconds = models.BigIntegerField(**BNULL) long_play_complete = models.BooleanField(**BNULL) + class Meta: + indexes = [ + models.Index(fields=["timestamp"]), + models.Index(fields=["media_type"]), + models.Index(fields=["source_id"]), + models.Index( + fields=["user", "in_progress", "played_to_completion", "is_paused"] + ), + ] + @classmethod def for_year(cls, user, year): return cls.objects.filter(timestamp__year=year, user=user).order_by( @@ -679,9 +646,7 @@ class Scrobble(TimeStampedModel): from scrobbles.models import Scrobble if self.logdata and self.logdata.serial_scrobble_id: - return Scrobble.objects.filter( - id=self.logdata.serial_scrobble_id - ).first() + return Scrobble.objects.filter(id=self.logdata.serial_scrobble_id).first() @property def finish_url(self) -> str: @@ -703,8 +668,12 @@ class Scrobble(TimeStampedModel): if self.media_obj: self.media_type = self.MediaType(self.media_obj.__class__.__name__) - if (self.timestamp and self.stop_timestamp) and (not self.playback_position_seconds or self.playback_position_seconds <= 0): - self.playback_position_seconds = (self.stop_timestamp - self.timestamp).seconds + if (self.timestamp and self.stop_timestamp) and ( + not self.playback_position_seconds or self.playback_position_seconds <= 0 + ): + self.playback_position_seconds = ( + self.stop_timestamp - self.timestamp + ).seconds return super(Scrobble, self).save(*args, **kwargs) @@ -715,9 +684,9 @@ class Scrobble(TimeStampedModel): return reverse("scrobbles:detail", kwargs={"uuid": self.uuid}) def push_to_archivebox(self): - pushable_media = hasattr( - self.media_obj, "push_to_archivebox" - ) and callable(self.media_obj.push_to_archivebox) + pushable_media = hasattr(self.media_obj, "push_to_archivebox") and callable( + self.media_obj.push_to_archivebox + ) if pushable_media and self.user.profile.archivebox_url: try: @@ -781,10 +750,7 @@ class Scrobble(TimeStampedModel): logger.info(f"Redirecting to {self.media_obj.url}") redirect_url = self.media_obj.url - if ( - self.media_type == self.MediaType.VIDEO - and self.media_obj.youtube_id - ): + if self.media_type == self.MediaType.VIDEO and self.media_obj.youtube_id: redirect_url = self.media_obj.youtube_link return redirect_url @@ -800,9 +766,7 @@ class Scrobble(TimeStampedModel): @property def local_stop_timestamp(self): if self.stop_timestamp: - return timezone.localtime( - self.stop_timestamp, timezone=self.tzinfo - ) + return timezone.localtime(self.stop_timestamp, timezone=self.tzinfo) @property def scrobble_media_key(self) -> str: @@ -930,9 +894,7 @@ class Scrobble(TimeStampedModel): long_play_secs = 0 if self.previous and not self.previous.long_play_complete: long_play_secs = self.previous.long_play_seconds or 0 - percent = int( - ((playback_seconds + long_play_secs) / run_time_secs) * 100 - ) + percent = int(((playback_seconds + long_play_secs) / run_time_secs) * 100) return percent @@ -977,9 +939,7 @@ class Scrobble(TimeStampedModel): expected_end = self.timestamp + datetime.timedelta( seconds=self.media_obj.run_time_seconds ) - expected_end_padded = expected_end + datetime.timedelta( - seconds=padding_seconds - ) + expected_end_padded = expected_end + datetime.timedelta(seconds=padding_seconds) # Take our start time, add our media length and an extra 30 min (1800s) is it still in the future? keep going is_in_progress = expected_end_padded > pendulum.now() logger.info( @@ -995,7 +955,10 @@ class Scrobble(TimeStampedModel): @property def can_be_updated(self) -> bool: - if self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values() and self.source != "readcomicsonline.ru": + if ( + self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values() + and self.source != "readcomicsonline.ru" + ): logger.info( "[scrobbling] cannot be updated, long play media", extra={ @@ -1039,11 +1002,9 @@ class Scrobble(TimeStampedModel): @classmethod def by_date(cls, media_type: str = "Track"): - cls.objects.filter(media_type=media_type).values( - "timestamp__date" - ).annotate(count=models.Count("id")).values( - "timestamp__date", "count" - ).order_by( + cls.objects.filter(media_type=media_type).values("timestamp__date").annotate( + count=models.Count("id") + ).values("timestamp__date", "count").order_by( "-count", ) @@ -1150,7 +1111,10 @@ class Scrobble(TimeStampedModel): if existing_by_source_id: logger.info( "[create_or_update] found existing scrobble by source_id, updating", - extra={"scrobble_id": existing_by_source_id.id, "source_id": source_id}, + extra={ + "scrobble_id": existing_by_source_id.id, + "source_id": source_id, + }, ) scrobble_data["playback_status"] = scrobble_data.pop("status", None) return existing_by_source_id.update(scrobble_data) @@ -1172,9 +1136,7 @@ class Scrobble(TimeStampedModel): logger.warning( f"[create_or_update] geoloc requires create_or_update_location" ) - scrobble = cls.create_or_update_location( - media, scrobble_data, user_id - ) + scrobble = cls.create_or_update_location(media, scrobble_data, user_id) return scrobble if not skip_in_progress_check or read_log_page: @@ -1187,9 +1149,7 @@ class Scrobble(TimeStampedModel): "scrobble_data": scrobble_data, }, ) - scrobble_data["playback_status"] = scrobble_data.pop( - "status", None - ) + scrobble_data["playback_status"] = scrobble_data.pop("status", None) # If it's marked as stopped, send it through our update mechanism, which will complete it if scrobble and ( scrobble.can_be_updated @@ -1207,7 +1167,7 @@ class Scrobble(TimeStampedModel): page_list.append( BookPageLogData( page_number=read_log_page, - start_ts=int(timezone.now().timestamp()) + start_ts=int(timezone.now().timestamp()), ) ) scrobble.log["page_data"] = page_list @@ -1220,7 +1180,14 @@ class Scrobble(TimeStampedModel): scrobble_data.pop("playback_status") if read_log_page: - scrobble_data["log"] = BookLogData(page_data=[BookPageLogData(page_number=read_log_page, start_ts=int(timezone.now().timestamp()))]) + scrobble_data["log"] = BookLogData( + page_data=[ + BookPageLogData( + page_number=read_log_page, + start_ts=int(timezone.now().timestamp()), + ) + ] + ) logger.info( f"[scrobbling] creating new scrobble", @@ -1231,7 +1198,9 @@ class Scrobble(TimeStampedModel): "source": source, }, ) - if mtype == cls.MediaType.FOOD and not scrobble_data.get("log", {}).get("calories", None): + if mtype == cls.MediaType.FOOD and not scrobble_data.get("log", {}).get( + "calories", None + ): if media.calories: scrobble_data["log"] = FoodLogData(calories=media.calories) @@ -1318,9 +1287,7 @@ class Scrobble(TimeStampedModel): if existing_locations := location.in_proximity(named=True): existing_location = existing_locations.first() ts = int(pendulum.now().timestamp()) - scrobble.log[ - ts - ] = f"Location {location.id} too close to this scrobble" + scrobble.log[ts] = f"Location {location.id} too close to this scrobble" scrobble.save(update_fields=["log"]) logger.info( f"[scrobbling] finished - found existing named location", @@ -1499,9 +1466,7 @@ class Scrobble(TimeStampedModel): """Returns true if our media is beyond our completion percent, unless our type is geolocation in which case we always return false """ - beyond_completion = ( - self.percent_played >= self.media_obj.COMPLETION_PERCENT - ) + beyond_completion = self.percent_played >= self.media_obj.COMPLETION_PERCENT if self.media_type == "GeoLocation": logger.info( diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index 6894c3e..2376c61 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -87,6 +87,7 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble: log = { "mopidy_source": post_data.get("mopidy_uri", "").split(":")[0], "raw_data": post_data, + "album": post_data.get("album", ""), } except IndexError: pass @@ -95,17 +96,14 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble: user_id, source="Mopidy", playback_position_seconds=int( - post_data.get(MOPIDY_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) - / 1000 + post_data.get(MOPIDY_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) / 1000 ), status=post_data.get(MOPIDY_POST_KEYS.get("STATUS"), ""), log=log, ) -def jellyfin_scrobble_media( - post_data: dict, user_id: int -) -> Optional[Scrobble]: +def jellyfin_scrobble_media(post_data: dict, user_id: int) -> Optional[Scrobble]: media_type = Scrobble.MediaType.VIDEO if post_data.pop("ItemType", "") in JELLYFIN_AUDIO_ITEM_TYPES: media_type = Scrobble.MediaType.TRACK @@ -123,13 +121,12 @@ def jellyfin_scrobble_media( ) return - timestamp = parse( - post_data.get(JELLYFIN_POST_KEYS.get("TIMESTAMP"), "") - ).replace(tzinfo=pytz.utc) + timestamp = parse(post_data.get(JELLYFIN_POST_KEYS.get("TIMESTAMP"), "")).replace( + tzinfo=pytz.utc + ) playback_position_seconds = int( - post_data.get(JELLYFIN_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) - / 10000000 + post_data.get(JELLYFIN_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) / 10000000 ) if media_type == Scrobble.MediaType.VIDEO: imdb_id = post_data.get("Provider_imdb", "") @@ -139,9 +136,7 @@ def jellyfin_scrobble_media( title=post_data.get("Name", ""), artist_name=post_data.get("Artist", ""), album_name=post_data.get("Album", ""), - run_time_seconds=convert_to_seconds( - post_data.get("RunTime", 900000) - ), + run_time_seconds=convert_to_seconds(post_data.get("RunTime", 900000)), ) # A hack because we don't worry about updating music ... we either finish it or we don't playback_position_seconds = 0 @@ -165,6 +160,7 @@ def jellyfin_scrobble_media( source_id=post_data.get(JELLYFIN_POST_KEYS.get("MEDIA_SOURCE_ID")), playback_position_seconds=playback_position_seconds, status=playback_status, + log={"album": post_data.get("Album", "")}, ) @@ -301,9 +297,7 @@ def manual_scrobble_book( if not page: page = 1 - logger.info( - "[scrobblers] Book page included in scrobble, should update!" - ) + logger.info("[scrobblers] Book page included in scrobble, should update!") source = READCOMICSONLINE_URL.replace("https://", "") @@ -420,9 +414,7 @@ def email_scrobble_board_game( if not location.name: location.name = location_dict.get("name") update_fields.append("name") - geoloc = GeoLocation.objects.filter( - title__icontains=location.name - ).first() + geoloc = GeoLocation.objects.filter(title__icontains=location.name).first() if geoloc: location.geo_location = geoloc update_fields.append("geo_location") @@ -474,9 +466,7 @@ def email_scrobble_board_game( log_data.pop("expansion_ids") if play_dict.get("locationRefId", False): - log_data["location_id"] = locations[ - play_dict.get("locationRefId") - ].id + log_data["location_id"] = locations[play_dict.get("locationRefId")].id if play_dict.get("rounds", False): log_data["rounds"] = play_dict.get("rounds") if play_dict.get("board", False): @@ -499,9 +489,7 @@ def email_scrobble_board_game( timestamp = parse(play_dict.get("playDate")) if hour and minute: logger.info(f"Scrobble playDate has manual start time {timestamp}") - timestamp = timestamp.replace( - hour=hour, minute=minute, second=second or 0 - ) + timestamp = timestamp.replace(hour=hour, minute=minute, second=second or 0) logger.info(f"Update to {timestamp}") profile = UserProfile.objects.filter(user_id=user_id).first() @@ -596,9 +584,7 @@ def manual_scrobble_from_url( scrobbler. Otherwise, return nothing.""" if RecipeScraperService.is_recipe_url(url): - return scrobble_from_recipe_website( - url, user_id, source=source, action=action - ) + return scrobble_from_recipe_website(url, user_id, source=source, action=action) content_key = "" domain = extract_domain(url) @@ -801,9 +787,7 @@ def emacs_scrobble_update_task( if not scrobble.log.get('notes"'): scrobble.log["notes"] = [] if note.get("timestamp") not in existing_note_ts: - scrobble.log["notes"].append( - {note.get("timestamp"): note.get("content")} - ) + scrobble.log["notes"].append({note.get("timestamp"): note.get("content")}) notes_updated = True if notes_updated: @@ -829,9 +813,7 @@ def emacs_scrobble_task( user_context_list: list[str] = [], ) -> Scrobble | None: orgmode_id = task_data.get("source_id") - title = get_title_from_labels( - task_data.get("labels", []), user_context_list - ) + title = get_title_from_labels(task_data.get("labels", []), user_context_list) task = Task.find_or_create(title) @@ -954,9 +936,7 @@ def manual_scrobble_webpage( ): if RecipeScraperService.is_recipe_url(url): - return scrobble_from_recipe_website( - url, user_id, source=source, action=action - ) + return scrobble_from_recipe_website(url, user_id, source=source, action=action) webpage = WebPage.find_or_create({"url": url}) diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index de6cd54..5574e0f 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -155,25 +155,17 @@ class RecentScrobbleList(ListView): next_date = date + timedelta(weeks=+2) prev_date = date + timedelta(weeks=-1) if date.isocalendar()[1] < today.isocalendar()[1]: - data[ - "next_link" - ] = f"?date={next_date.strftime('%Y-W%W')}" + data["next_link"] = f"?date={next_date.strftime('%Y-W%W')}" data["prev_link"] = f"?date={prev_date.strftime('%Y-W%W')}" - data[ - "title" - ] = f"Week {date.strftime('%-W')} of {date.year}" + data["title"] = f"Week {date.strftime('%-W')} of {date.year}" data = data | Scrobble.as_dict_by_type( - Scrobble.for_week( - user_id, date.year, date.isocalendar()[1] - ) + Scrobble.for_week(user_id, date.year, date.isocalendar()[1]) ) elif date_str == "this_month" or date_str.count("-") == 1: next_date = date + relativedelta(months=1) prev_date = date + relativedelta(months=-1) if date.month < today.month: - data[ - "next_link" - ] = f"?date={next_date.strftime('%Y-%m')}" + data["next_link"] = f"?date={next_date.strftime('%Y-%m')}" data["title"] = f"{date.strftime('%B %Y')}" data["prev_link"] = f"?date={prev_date.strftime('%Y-%m')}" data = data | Scrobble.as_dict_by_type( @@ -182,24 +174,16 @@ class RecentScrobbleList(ListView): elif date_str == "today" or date_str.count("-") == 2: next_date = date + timedelta(days=1) prev_date = date - timedelta(days=1) - data[ - "prev_link" - ] = f"?date={prev_date.strftime('%Y-%m-%d')}" + data["prev_link"] = f"?date={prev_date.strftime('%Y-%m-%d')}" if date < today: - data[ - "next_link" - ] = f"?date={next_date.strftime('%Y-%m-%d')}" + data["next_link"] = f"?date={next_date.strftime('%Y-%m-%d')}" if date == today: data["title"] = "Today" else: data["title"] = f"{date.strftime('%Y-%m-%d')}" - data[ - "today_link" - ] = f"?date={today.strftime('%Y-%m-%d')}" + data["today_link"] = f"?date={today.strftime('%Y-%m-%d')}" data = data | Scrobble.as_dict_by_type( - Scrobble.for_day( - user_id, date.year, date.month, date.day - ) + Scrobble.for_day(user_id, date.year, date.month, date.day) ) elif date_str == "this_year" or date_str.count("-") == 0: next_date = date + relativedelta(years=+1) @@ -222,9 +206,7 @@ class RecentScrobbleList(ListView): data = data | Scrobble.as_dict_by_type( Scrobble.for_day(user_id, date.year, date.month, date.day) ) - data[ - "today_link" - ] = "" # f"?date={today.strftime('%Y-%m-%d')}" + data["today_link"] = "" # f"?date={today.strftime('%Y-%m-%d')}" data["active_imports"] = AudioScrobblerTSVImport.objects.filter( processing_started__isnull=False, @@ -232,7 +214,9 @@ class RecentScrobbleList(ListView): user=self.request.user, ) data["counts"] = [] # scrobble_counts(user) - data["daily_calories"] = get_daily_calories_for_user_by_day(self.request.user.id, date) + data["daily_calories"] = get_daily_calories_for_user_by_day( + self.request.user.id, date + ) else: data["weekly_data"] = week_of_scrobbles() data["counts"] = scrobble_counts() @@ -251,9 +235,7 @@ class ScrobbleLongPlaysView(TemplateView): def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) context_data["view"] = self.request.GET.get("view", "grid") - context_data["in_progress"] = get_long_plays_in_progress( - self.request.user - ) + context_data["in_progress"] = get_long_plays_in_progress(self.request.user) context_data["completed"] = get_long_plays_completed(self.request.user) return context_data @@ -330,9 +312,7 @@ class ManualScrobbleView(FormView): scrobble_fn = MANUAL_SCROBBLE_FNS[key] scrobble = eval(scrobble_fn)(item_id, self.request.user.id) - return HttpResponseRedirect( - scrobble.redirect_url(self.request.user.id) - ) + return HttpResponseRedirect(scrobble.redirect_url(self.request.user.id)) class JsonableResponseMixin: @@ -378,9 +358,7 @@ class AudioScrobblerImportCreateView( return HttpResponseRedirect(self.request.META.get("HTTP_REFERER")) -class KoReaderImportCreateView( - LoginRequiredMixin, JsonableResponseMixin, CreateView -): +class KoReaderImportCreateView(LoginRequiredMixin, JsonableResponseMixin, CreateView): model = KoReaderImport fields = ["sqlite_file"] template_name = "scrobbles/upload_form.html" @@ -439,9 +417,7 @@ def web_scrobbler_webhook(request): playing_state = "started" if request.POST.get("isPlaying") == "false": playing_state = "stopped" - scrobble = web_scrobbler_scrobble_media( - youtube_id, user_id, status=playing_state - ) + scrobble = web_scrobbler_scrobble_media(youtube_id, user_id, status=playing_state) if not scrobble: logger.warning( "[web_scrobbler_webhook] failed", @@ -566,18 +542,12 @@ def import_audioscrobbler_file(request): scrobbles_created = [] # tsv_file = request.FILES[0] - file_serializer = serializers.AudioScrobblerTSVImportSerializer( - data=request.data - ) + file_serializer = serializers.AudioScrobblerTSVImportSerializer(data=request.data) if file_serializer.is_valid(): import_file = file_serializer.save() - return Response( - {"scrobble_ids": scrobbles_created}, status=status.HTTP_200_OK - ) + return Response({"scrobble_ids": scrobbles_created}, status=status.HTTP_200_OK) else: - return Response( - file_serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) + return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST) @permission_classes([IsAuthenticated]) @@ -623,9 +593,9 @@ def scrobble_start(request, uuid): request, messages.ERROR, f"Media with uuid {uuid} not found." ) - if ( - user.profile.redirect_to_webpage - and (media_obj.__class__.__name__ == Scrobble.MediaType.WEBPAGE or media_obj.__class__.__name__ == Scrobble.MediaType.BOOK) + if user.profile.redirect_to_webpage and ( + media_obj.__class__.__name__ == Scrobble.MediaType.WEBPAGE + or media_obj.__class__.__name__ == Scrobble.MediaType.BOOK ): logger.info(f"Redirecting to {media_obj} detail page") return HttpResponseRedirect(media_obj.url) @@ -754,9 +724,7 @@ class ChartRecordView(TemplateView): ).order_by("rank") if charts.count() == 0: - ChartRecord.build( - user=self.request.user, model_str=media_type, **kwargs - ) + ChartRecord.build(user=self.request.user, model_str=media_type, **kwargs) charts = ChartRecord.objects.filter( media_filter, user=self.request.user, **kwargs ).order_by("rank") @@ -788,9 +756,7 @@ class ChartRecordView(TemplateView): media_type = self.request.GET.get("media", "Track") user = self.request.user params = {} - context_data["chart_type"] = self.request.GET.get( - "chart_type", "maloja" - ) + context_data["chart_type"] = self.request.GET.get("chart_type", "maloja") context_data["artist_charts"] = {} if not date: @@ -855,9 +821,7 @@ class ChartRecordView(TemplateView): params["day"] = day month_str = calendar.month_name[month] name = f"Chart for {month_str} {day}, {year}" - in_progress = ( - now.month == month and now.year == year and now.day == day - ) + in_progress = now.month == month and now.year == year and now.day == day media_filter = self.get_media_filter("Track") track_charts = ChartRecord.objects.filter( @@ -869,17 +833,13 @@ class ChartRecordView(TemplateView): ).order_by("rank") if track_charts.count() == 0 and not in_progress: - ChartRecord.build( - user=self.request.user, model_str="Track", **params - ) + ChartRecord.build(user=self.request.user, model_str="Track", **params) media_filter = self.get_media_filter("Track") track_charts = ChartRecord.objects.filter( media_filter, user=self.request.user, **params ).order_by("rank") if artist_charts.count() == 0 and not in_progress: - ChartRecord.build( - user=self.request.user, model_str="Artist", **params - ) + ChartRecord.build(user=self.request.user, model_str="Artist", **params) media_filter = self.get_media_filter("Artist") artist_charts = ChartRecord.objects.filter( media_filter, user=self.request.user, **params @@ -900,38 +860,24 @@ class ScrobbleStatusView(LoginRequiredMixin, TemplateView): def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) user_scrobble_qs = Scrobble.objects.filter().order_by("-timestamp") - progress_plays = user_scrobble_qs.filter( - in_progress=True, is_paused=False - ) + progress_plays = user_scrobble_qs.filter(in_progress=True, is_paused=False) data["listening"] = progress_plays.filter( Q(track__isnull=False) | Q(podcast_episode__isnull=False) ).first() data["watching"] = progress_plays.filter(video__isnull=False).first() - data["going"] = progress_plays.filter( - geo_location__isnull=False - ).first() - data["playing"] = progress_plays.filter( - board_game__isnull=False - ).first() - data["sporting"] = progress_plays.filter( - sport_event__isnull=False - ).first() - data["browsing"] = progress_plays.filter( - web_page__isnull=False - ).first() - data["participating"] = progress_plays.filter( - life_event__isnull=False - ).first() + data["going"] = progress_plays.filter(geo_location__isnull=False).first() + data["playing"] = progress_plays.filter(board_game__isnull=False).first() + data["sporting"] = progress_plays.filter(sport_event__isnull=False).first() + data["browsing"] = progress_plays.filter(web_page__isnull=False).first() + data["participating"] = progress_plays.filter(life_event__isnull=False).first() data["working"] = progress_plays.filter(task__isnull=False).first() long_plays = user_scrobble_qs.filter( long_play_complete=False, played_to_completion=True ) data["reading"] = long_plays.filter(book__isnull=False).first() - data["sessioning"] = long_plays.filter( - video_game__isnull=False - ).first() + data["sessioning"] = long_plays.filter(video_game__isnull=False).first() return data @@ -949,14 +895,16 @@ class ScrobbleDetailView(DetailView): log = self.object.log or {} initial_notes = log.get("notes", []) - if isinstance(initial_notes, list) and len(initial_notes) > 0 and isinstance(initial_notes[0], dict): + if ( + isinstance(initial_notes, list) + and len(initial_notes) > 0 + and isinstance(initial_notes[0], dict) + ): notes_str = note_list_to_str(notes) else: notes_str = "\n".join(initial_notes) if initial_notes else "" - notes_str_fixed = notes_str.encode("utf-8").decode( - "unicode_escape" - ) + notes_str_fixed = notes_str.encode("utf-8").decode("unicode_escape") log["notes"] = notes_str_fixed return FormClass(initial=log) @@ -975,9 +923,7 @@ class ScrobbleDetailView(DetailView): data[field_name] = original_value if data.get("with_people_ids", False): - data["with_people_ids"] = [ - p.id for p in data["with_people_ids"] - ] + data["with_people_ids"] = [p.id for p in data["with_people_ids"]] if data.get("platform_id", False): data["platform_id"] = data["platform_id"].id diff --git a/vrobbler/settings.py b/vrobbler/settings.py index ae87a42..f269f19 100644 --- a/vrobbler/settings.py +++ b/vrobbler/settings.py @@ -37,9 +37,7 @@ TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" TAGGIT_CASE_INSENSITIVE = True -KEEP_DETAILED_SCROBBLE_LOGS = os.getenv( - "VROBBLER_KEEP_DETAILED_SCROBBLE_LOGS", False -) +KEEP_DETAILED_SCROBBLE_LOGS = os.getenv("VROBBLER_KEEP_DETAILED_SCROBBLE_LOGS", False) # Key must be 16, 24 or 32 bytes long and will be converted to a byte stream ENCRYPTED_FIELD_KEY = os.getenv( @@ -54,9 +52,7 @@ DELETE_STALE_SCROBBLES = ( ) # Used to dump data coming from srobbling sources, helpful for building new inputs -DUMP_REQUEST_DATA = ( - os.getenv("VROBBLER_DUMP_REQUEST_DATA", "false").lower() in TRUTHY -) +DUMP_REQUEST_DATA = os.getenv("VROBBLER_DUMP_REQUEST_DATA", "false").lower() in TRUTHY USDA_API_KEY = os.getenv("VROBBLER_USDA_API_KEY") THESPORTSDB_API_KEY = os.getenv("VROBBLER_THESPORTSDB_API_KEY", "2") @@ -72,9 +68,7 @@ COMICVINE_API_KEY = os.getenv("VROBBLER_COMICVINE_API_KEY") BGG_ACCESS_TOKEN = os.getenv("VROBBLER_BGG_ACCESS_TOKEN", "") GEOLOC_ACCURACY = os.getenv("VROBBLER_GEOLOC_ACCURACY", 3) GEOLOC_PROXIMITY = os.getenv("VROBBLER_GEOLOC_PROXIMITY", "0.0001") -POINTS_FOR_MOVEMENT_HISTORY = os.getenv( - "VROBBLER_POINTS_FOR_MOVEMENT_HISTORY", 3 -) +POINTS_FOR_MOVEMENT_HISTORY = os.getenv("VROBBLER_POINTS_FOR_MOVEMENT_HISTORY", 3) TODOIST_CLIENT_ID = os.getenv("VROBBLER_TODOIST_CLIENT_ID", "") TODOIST_CLIENT_SECRET = os.getenv("VROBBLER_TODOIST_CLIENT_SECRET", "") @@ -97,9 +91,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" TIME_ZONE = os.getenv("VROBBLER_TIME_ZONE", "America/New_York") ALLOWED_HOSTS = ["*"] -CSRF_TRUSTED_ORIGINS = [ - os.getenv("VROBBLER_TRUSTED_ORIGINS", "http://localhost:8000") -] +CSRF_TRUSTED_ORIGINS = [os.getenv("VROBBLER_TRUSTED_ORIGINS", "http://localhost:8000")] X_FRAME_OPTIONS = "SAMEORIGIN" REDIS_URL = os.getenv("VROBBLER_REDIS_URL", None) @@ -108,9 +100,7 @@ if REDIS_URL: else: print("Eagerly running all tasks") -CELERY_TASK_ALWAYS_EAGER = ( - os.getenv("VROBBLER_SKIP_CELERY", "false").lower() in TRUTHY -) +CELERY_TASK_ALWAYS_EAGER = os.getenv("VROBBLER_SKIP_CELERY", "false").lower() in TRUTHY CELERY_BROKER_URL = REDIS_URL if REDIS_URL else "memory://localhost/" CELERY_RESULT_BACKEND = "django-db" CELERY_TIMEZONE = os.getenv("VROBBLER_TIME_ZONE", "America/New_York") @@ -210,9 +200,7 @@ DATABASES = { } if TESTING: - DATABASES = { - "default": dj_database_url.config(default="sqlite:///testdb.sqlite3") - } + DATABASES = {"default": dj_database_url.config(default="sqlite:///testdb.sqlite3")} db_str = "" if "sqlite" in DATABASES["default"]["ENGINE"]: @@ -310,9 +298,7 @@ else: STATIC_ROOT = os.getenv( "VROBBLER_STATIC_ROOT", os.path.join(PROJECT_ROOT, "static") ) - MEDIA_ROOT = os.getenv( - "VROBBLER_MEDIA_ROOT", os.path.join(PROJECT_ROOT, "media") - ) + MEDIA_ROOT = os.getenv("VROBBLER_MEDIA_ROOT", os.path.join(PROJECT_ROOT, "media")) STATIC_URL = os.getenv("VROBBLER_STATIC_URL", "/static/") MEDIA_URL = os.getenv("VROBBLER_MEDIA_URL", "/media/") @@ -396,9 +382,7 @@ LOGGING = { }, } -LOG_TO_CONSOLE = ( - os.getenv("VROBBLER_LOG_TO_CONSOLE", "false").lower() in TRUTHY -) +LOG_TO_CONSOLE = os.getenv("VROBBLER_LOG_TO_CONSOLE", "false").lower() in TRUTHY if LOG_TO_CONSOLE: LOGGING["loggers"]["django"]["handlers"] = ["console"] LOGGING["loggers"]["vrobbler"]["handlers"] = ["console"] diff --git a/vrobbler/urls.py b/vrobbler/urls.py index 226b39a..a43b035 100644 --- a/vrobbler/urls.py +++ b/vrobbler/urls.py @@ -1,3 +1,5 @@ +from django.conf import settings +from django.conf.urls.static import static from django.contrib import admin from django.urls import include, path from oauth2_provider import urls as oauth2_urls @@ -6,7 +8,12 @@ from rest_framework import routers import vrobbler.apps.scrobbles.views as scrobbles_views from vrobbler.apps.boardgames import urls as boardgame_urls -from vrobbler.apps.boardgames.api.views import BoardGameViewSet, BoardGameDesignerViewSet, BoardGamePublisherViewSet, BoardGameLocationViewSet +from vrobbler.apps.boardgames.api.views import ( + BoardGameViewSet, + BoardGameDesignerViewSet, + BoardGamePublisherViewSet, + BoardGameLocationViewSet, +) from vrobbler.apps.books import urls as book_urls from vrobbler.apps.books.api.views import AuthorViewSet, BookViewSet @@ -31,7 +38,11 @@ from vrobbler.apps.music.api.views import ( ) from vrobbler.apps.podcasts import urls as podcast_urls -from vrobbler.apps.podcasts.api.views import ProducerViewSet, PodcastViewSet, PodcastEpisodeViewSet +from vrobbler.apps.podcasts.api.views import ( + ProducerViewSet, + PodcastViewSet, + PodcastEpisodeViewSet, +) from vrobbler.apps.scrobbles import urls as scrobble_urls from vrobbler.apps.scrobbles.api.views import ( @@ -60,14 +71,25 @@ from vrobbler.apps.trails import urls as trails_urls from vrobbler.apps.trails.api.views import TrailViewSet from vrobbler.apps.beers import urls as beers_urls -from vrobbler.apps.beers.api.views import BeerViewSet, BeerProducerViewSet, BeerStyleViewSet +from vrobbler.apps.beers.api.views import ( + BeerViewSet, + BeerProducerViewSet, + BeerStyleViewSet, +) from vrobbler.apps.puzzles import urls as puzzles_urls -from vrobbler.apps.puzzles.api.views import PuzzleViewSet, PuzzleManufacturerViewSet +from vrobbler.apps.puzzles.api.views import ( + PuzzleViewSet, + PuzzleManufacturerViewSet, +) from vrobbler.apps.videogames import urls as videogame_urls from vrobbler.apps.videos import urls as video_urls -from vrobbler.apps.videos.api.views import ChannelViewSet, SeriesViewSet, VideoViewSet +from vrobbler.apps.videos.api.views import ( + ChannelViewSet, + SeriesViewSet, + VideoViewSet, +) from vrobbler.apps.bricksets import urls as bricksets_urls from vrobbler.apps.bricksets.api.views import BrickSetViewSet @@ -149,3 +171,10 @@ urlpatterns = [ "", scrobbles_views.RecentScrobbleList.as_view(), name="vrobbler-home" ), ] +if settings.DEBUG: + urlpatterns = urlpatterns + static( + settings.STATIC_URL, document_root=settings.STATIC_ROOT + ) + urlpatterns = urlpatterns + static( + settings.MEDIA_URL, document_root=settings.MEDIA_ROOT + )