From 94f1396f2e2023f7e5799e3c9b6169266a603829 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Sat, 4 Mar 2023 17:29:25 -0500 Subject: [PATCH] Blacken quotes --- manage.py | 4 +- pyproject.toml | 1 - tests/scrobbles_tests/conftest.py | 4 +- tests/scrobbles_tests/test_aggregators.py | 30 ++-- tests/scrobbles_tests/test_views.py | 38 ++-- tests/videos_tests/test_imdb.py | 2 +- vrobbler/__init__.py | 2 +- vrobbler/apps/books/api/views.py | 4 +- vrobbler/apps/books/koreader.py | 4 +- vrobbler/apps/books/models.py | 4 +- vrobbler/apps/books/utils.py | 6 +- vrobbler/apps/music/admin.py | 2 +- vrobbler/apps/music/aggregators.py | 30 ++-- vrobbler/apps/music/api/views.py | 6 +- vrobbler/apps/music/apps.py | 2 +- vrobbler/apps/music/constants.py | 28 +-- vrobbler/apps/music/lastfm.py | 14 +- vrobbler/apps/music/models.py | 90 +++++----- vrobbler/apps/music/musicbrainz.py | 62 +++---- vrobbler/apps/music/theaudiodb.py | 58 +++--- vrobbler/apps/music/urls.py | 20 +-- vrobbler/apps/music/utils.py | 18 +- vrobbler/apps/music/views.py | 20 +-- vrobbler/apps/podcasts/apps.py | 2 +- vrobbler/apps/podcasts/models.py | 8 +- vrobbler/apps/profiles/api/serializers.py | 4 +- vrobbler/apps/profiles/api/views.py | 4 +- vrobbler/apps/scrobbles/admin.py | 14 +- vrobbler/apps/scrobbles/api/views.py | 8 +- vrobbler/apps/scrobbles/apps.py | 2 +- vrobbler/apps/scrobbles/context_processors.py | 2 +- vrobbler/apps/scrobbles/export.py | 18 +- vrobbler/apps/scrobbles/forms.py | 12 +- vrobbler/apps/scrobbles/models.py | 84 ++++----- vrobbler/apps/scrobbles/scrobblers.py | 6 +- vrobbler/apps/scrobbles/stats.py | 50 +++--- .../apps/scrobbles/templatetags/urlreplace.py | 2 +- vrobbler/apps/scrobbles/tsv.py | 8 +- vrobbler/apps/scrobbles/urls.py | 56 +++--- vrobbler/apps/scrobbles/utils.py | 22 +-- vrobbler/apps/scrobbles/views.py | 166 +++++++++--------- vrobbler/apps/sports/admin.py | 4 +- vrobbler/apps/sports/api/views.py | 14 +- vrobbler/apps/sports/models.py | 46 ++--- vrobbler/apps/sports/thesportsdb.py | 32 ++-- vrobbler/apps/sports/urls.py | 10 +- vrobbler/apps/sports/utils.py | 4 +- vrobbler/apps/sports/views.py | 2 +- vrobbler/apps/videos/admin.py | 2 +- vrobbler/apps/videos/api/views.py | 4 +- vrobbler/apps/videos/apps.py | 2 +- vrobbler/apps/videos/imdb.py | 22 +-- vrobbler/apps/videos/models.py | 16 +- vrobbler/apps/videos/urls.py | 14 +- vrobbler/apps/videos/views.py | 4 +- vrobbler/asgi.py | 2 +- vrobbler/cli.py | 8 +- vrobbler/settings.py | 40 ++--- vrobbler/urls.py | 42 ++--- vrobbler/wsgi.py | 2 +- 60 files changed, 593 insertions(+), 594 deletions(-) diff --git a/manage.py b/manage.py index 12d9917..8a3ff8f 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'vrobbler.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vrobbler.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/pyproject.toml b/pyproject.toml index 55e708b..1e95b65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,6 @@ DJANGO_SETTINGS_MODULE='vrobbler.settings' [tool.black] line-length = 79 -skip-string-normalization = true target-version = ["py39", "py310"] include = ".py$" exclude = "migrations" diff --git a/tests/scrobbles_tests/conftest.py b/tests/scrobbles_tests/conftest.py index 4fb3e19..48924a8 100644 --- a/tests/scrobbles_tests/conftest.py +++ b/tests/scrobbles_tests/conftest.py @@ -24,7 +24,7 @@ class MopidyRequest: def __init__(self, **kwargs): self.request_data = { - "name": kwargs.get('name', self.name), + "name": kwargs.get("name", self.name), "artist": kwargs.get("artist", self.artist), "album": kwargs.get("album", self.album), "track_number": int(kwargs.get("track_number", self.track_number)), @@ -61,7 +61,7 @@ class MopidyRequest: @pytest.fixture def valid_auth_token(): - user = User.objects.create(email='test@exmaple.com') + user = User.objects.create(email="test@exmaple.com") return Token.objects.create(user=user).key diff --git a/tests/scrobbles_tests/test_aggregators.py b/tests/scrobbles_tests/test_aggregators.py index 59a67af..ea9251f 100644 --- a/tests/scrobbles_tests/test_aggregators.py +++ b/tests/scrobbles_tests/test_aggregators.py @@ -11,11 +11,11 @@ from scrobbles.models import Scrobble def build_scrobbles(client, request_data, num=7, spacing=2): - url = reverse('scrobbles:mopidy-webhook') - user = get_user_model().objects.create(username='Test User') - UserProfile.objects.create(user=user, timezone='US/Eastern') + url = reverse("scrobbles:mopidy-webhook") + user = get_user_model().objects.create(username="Test User") + UserProfile.objects.create(user=user, timezone="US/Eastern") for i in range(num): - client.post(url, request_data, content_type='application/json') + client.post(url, request_data, content_type="application/json") s = Scrobble.objects.last() s.user = user s.timestamp = timezone.now() - timedelta(days=i * spacing) @@ -30,11 +30,11 @@ def test_scrobble_counts_data(client, mopidy_track_request_data): user = get_user_model().objects.first() count_dict = scrobble_counts(user) assert count_dict == { - 'alltime': 7, - 'month': 2, - 'today': 1, - 'week': 3, - 'year': 7, + "alltime": 7, + "month": 2, + "today": 1, + "week": 3, + "year": 7, } @@ -58,7 +58,7 @@ def test_top_tracks_by_day(client, mopidy_track_request_data): def test_top_tracks_by_week(client, mopidy_track_request_data): build_scrobbles(client, mopidy_track_request_data, 7, 1) user = get_user_model().objects.first() - tops = live_charts(user, chart_period='week') + tops = live_charts(user, chart_period="week") assert tops[0].title == "Same in the End" @@ -66,7 +66,7 @@ def test_top_tracks_by_week(client, mopidy_track_request_data): def test_top_tracks_by_month(client, mopidy_track_request_data): build_scrobbles(client, mopidy_track_request_data, 7, 1) user = get_user_model().objects.first() - tops = live_charts(user, chart_period='month') + tops = live_charts(user, chart_period="month") assert tops[0].title == "Same in the End" @@ -74,7 +74,7 @@ def test_top_tracks_by_month(client, mopidy_track_request_data): def test_top_tracks_by_year(client, mopidy_track_request_data): build_scrobbles(client, mopidy_track_request_data, 7, 1) user = get_user_model().objects.first() - tops = live_charts(user, chart_period='year') + tops = live_charts(user, chart_period="year") assert tops[0].title == "Same in the End" @@ -82,7 +82,7 @@ def test_top_tracks_by_year(client, mopidy_track_request_data): def test_top__artists_by_week(client, mopidy_track_request_data): build_scrobbles(client, mopidy_track_request_data, 7, 1) user = get_user_model().objects.first() - tops = live_charts(user, chart_period='week', media_type="Artist") + tops = live_charts(user, chart_period="week", media_type="Artist") assert tops[0].name == "Sublime" @@ -90,7 +90,7 @@ def test_top__artists_by_week(client, mopidy_track_request_data): def test_top__artists_by_month(client, mopidy_track_request_data): build_scrobbles(client, mopidy_track_request_data, 7, 1) user = get_user_model().objects.first() - tops = live_charts(user, chart_period='month', media_type="Artist") + tops = live_charts(user, chart_period="month", media_type="Artist") assert tops[0].name == "Sublime" @@ -98,5 +98,5 @@ def test_top__artists_by_month(client, mopidy_track_request_data): def test_top__artists_by_year(client, mopidy_track_request_data): build_scrobbles(client, mopidy_track_request_data, 7, 1) user = get_user_model().objects.first() - tops = live_charts(user, chart_period='year', media_type="Artist") + tops = live_charts(user, chart_period="year", media_type="Artist") assert tops[0].name == "Sublime" diff --git a/tests/scrobbles_tests/test_views.py b/tests/scrobbles_tests/test_views.py index 9adce08..3c28def 100644 --- a/tests/scrobbles_tests/test_views.py +++ b/tests/scrobbles_tests/test_views.py @@ -9,21 +9,21 @@ from scrobbles.models import Scrobble @pytest.mark.django_db def test_get_not_allowed_from_mopidy(client, valid_auth_token): - url = reverse('scrobbles:mopidy-webhook') - headers = {'Authorization': f'Token {valid_auth_token}'} + url = reverse("scrobbles:mopidy-webhook") + headers = {"Authorization": f"Token {valid_auth_token}"} response = client.get(url, headers=headers) assert response.status_code == 405 @pytest.mark.django_db def test_bad_mopidy_request_data(client, valid_auth_token): - url = reverse('scrobbles:mopidy-webhook') - headers = {'Authorization': f'Token {valid_auth_token}'} + url = reverse("scrobbles:mopidy-webhook") + headers = {"Authorization": f"Token {valid_auth_token}"} response = client.post(url, headers) assert response.status_code == 400 assert ( - response.data['detail'] - == 'JSON parse error - Expecting value: line 1 column 1 (char 0)' + response.data["detail"] + == "JSON parse error - Expecting value: line 1 column 1 (char 0)" ) @@ -31,16 +31,16 @@ def test_bad_mopidy_request_data(client, valid_auth_token): def test_scrobble_mopidy_track( client, mopidy_track_request_data, valid_auth_token ): - url = reverse('scrobbles:mopidy-webhook') - headers = {'Authorization': f'Token {valid_auth_token}'} + url = reverse("scrobbles:mopidy-webhook") + headers = {"Authorization": f"Token {valid_auth_token}"} response = client.post( url, mopidy_track_request_data, - content_type='application/json', + content_type="application/json", headers=headers, ) assert response.status_code == 200 - assert response.data == {'scrobble_id': 1} + assert response.data == {"scrobble_id": 1} scrobble = Scrobble.objects.get(id=1) assert scrobble.media_obj.__class__ == Track @@ -54,23 +54,23 @@ def test_scrobble_mopidy_same_track_different_album( mopidy_track_diff_album_request_data, valid_auth_token, ): - url = reverse('scrobbles:mopidy-webhook') - headers = {'Authorization': f'Token {valid_auth_token}'} + url = reverse("scrobbles:mopidy-webhook") + headers = {"Authorization": f"Token {valid_auth_token}"} response = client.post( url, mopidy_track_request_data, - content_type='application/json', + content_type="application/json", headers=headers, ) assert response.status_code == 200 - assert response.data == {'scrobble_id': 1} + assert response.data == {"scrobble_id": 1} scrobble = Scrobble.objects.get(id=1) assert scrobble.media_obj.album.name == "Sublime" response = client.post( url, mopidy_track_diff_album_request_data, - content_type='application/json', + content_type="application/json", ) scrobble = Scrobble.objects.get(id=2) @@ -83,16 +83,16 @@ def test_scrobble_mopidy_same_track_different_album( def test_scrobble_mopidy_podcast( client, mopidy_podcast_request_data, valid_auth_token ): - url = reverse('scrobbles:mopidy-webhook') - headers = {'Authorization': f'Token {valid_auth_token}'} + url = reverse("scrobbles:mopidy-webhook") + headers = {"Authorization": f"Token {valid_auth_token}"} response = client.post( url, mopidy_podcast_request_data, - content_type='application/json', + content_type="application/json", headers=headers, ) assert response.status_code == 200 - assert response.data == {'scrobble_id': 1} + assert response.data == {"scrobble_id": 1} scrobble = Scrobble.objects.get(id=1) assert scrobble.media_obj.__class__ == Episode diff --git a/tests/videos_tests/test_imdb.py b/tests/videos_tests/test_imdb.py index 8f43e9b..8879730 100644 --- a/tests/videos_tests/test_imdb.py +++ b/tests/videos_tests/test_imdb.py @@ -5,7 +5,7 @@ from videos.imdb import lookup_video_from_imdb @pytest.mark.skip(reason="Need to sort out third party API testing") def test_lookup_imdb_bad_id(caplog): - data = lookup_video_from_imdb('3409324') + data = lookup_video_from_imdb("3409324") assert data is None assert caplog.records[0].levelname == "WARNING" assert caplog.records[0].msg == "IMDB ID should begin with 'tt' 3409324" diff --git a/vrobbler/__init__.py b/vrobbler/__init__.py index 15d7c50..5568b6d 100644 --- a/vrobbler/__init__.py +++ b/vrobbler/__init__.py @@ -2,4 +2,4 @@ # Django starts so that shared_task will use this app. from .celery import app as celery_app -__all__ = ('celery_app',) +__all__ = ("celery_app",) diff --git a/vrobbler/apps/books/api/views.py b/vrobbler/apps/books/api/views.py index 6cdb4de..1f1a261 100644 --- a/vrobbler/apps/books/api/views.py +++ b/vrobbler/apps/books/api/views.py @@ -8,12 +8,12 @@ from books.models import Author, Book class AuthorViewSet(viewsets.ModelViewSet): - queryset = Author.objects.all().order_by('-created') + queryset = Author.objects.all().order_by("-created") serializer_class = AuthorSerializer permission_classes = [permissions.IsAuthenticated] class BookViewSet(viewsets.ModelViewSet): - queryset = Book.objects.all().order_by('-created') + queryset = Book.objects.all().order_by("-created") serializer_class = BookSerializer permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/apps/books/koreader.py b/vrobbler/apps/books/koreader.py index 07ee531..6b0948e 100644 --- a/vrobbler/apps/books/koreader.py +++ b/vrobbler/apps/books/koreader.py @@ -46,7 +46,7 @@ def process_koreader_sqlite_file(sqlite_file_path, user_id): book_table = cur.execute("SELECT * FROM book") new_scrobbles = [] for book_row in book_table: - authors = book_row[KoReaderBookColumn.AUTHORS.value].split('\n') + authors = book_row[KoReaderBookColumn.AUTHORS.value].split("\n") author_list = [] for author_str in authors: logger.debug(f"Looking up author {author_str}") @@ -119,6 +119,6 @@ def process_koreader_sqlite_file(sqlite_file_path, user_id): created = Scrobble.objects.bulk_create(new_scrobbles) logger.info( f"Created {len(created)} scrobbles", - extra={'created_scrobbles': created}, + extra={"created_scrobbles": created}, ) return created diff --git a/vrobbler/apps/books/models.py b/vrobbler/apps/books/models.py index ad53383..2e3b7f1 100644 --- a/vrobbler/apps/books/models.py +++ b/vrobbler/apps/books/models.py @@ -28,7 +28,7 @@ class Author(TimeStampedModel): class Book(ScrobblableMixin): - COMPLETION_PERCENT = getattr(settings, 'BOOK_COMPLETION_PERCENT', 95) + COMPLETION_PERCENT = getattr(settings, "BOOK_COMPLETION_PERCENT", 95) title = models.CharField(max_length=255) authors = models.ManyToManyField(Author) @@ -59,7 +59,7 @@ class Book(ScrobblableMixin): return self.authors.first() def get_absolute_url(self): - return reverse("books:book_detail", kwargs={'slug': self.uuid}) + return reverse("books:book_detail", kwargs={"slug": self.uuid}) @property def pages_for_completion(self) -> int: diff --git a/vrobbler/apps/books/utils.py b/vrobbler/apps/books/utils.py index 16ef6cd..d15c88d 100644 --- a/vrobbler/apps/books/utils.py +++ b/vrobbler/apps/books/utils.py @@ -26,12 +26,12 @@ def lookup_book_from_openlibrary(title: str, author: str = None) -> dict: results = json.loads(response.content) - if len(results.get('docs')) == 0: + if len(results.get("docs")) == 0: logger.warn(f"No results found from OL for {title}") return {} - top = results.get('docs')[0] - if author and author not in top['author_name']: + top = results.get("docs")[0] + if author and author not in top["author_name"]: logger.warn( f"Lookup for {title} found top result with mismatched author" ) diff --git a/vrobbler/apps/music/admin.py b/vrobbler/apps/music/admin.py index 69b4b4a..58a2eee 100644 --- a/vrobbler/apps/music/admin.py +++ b/vrobbler/apps/music/admin.py @@ -22,7 +22,7 @@ class AlbumAdmin(admin.ModelAdmin): ) ordering = ("name",) filter_horizontal = [ - 'artists', + "artists", ] diff --git a/vrobbler/apps/music/aggregators.py b/vrobbler/apps/music/aggregators.py index 15c79f6..38aedc8 100644 --- a/vrobbler/apps/music/aggregators.py +++ b/vrobbler/apps/music/aggregators.py @@ -29,24 +29,24 @@ def scrobble_counts(user=None): user_filter, played_to_completion=True ) data = {} - data['today'] = finished_scrobbles_qs.filter( + data["today"] = finished_scrobbles_qs.filter( timestamp__gte=start_of_today ).count() - data['week'] = finished_scrobbles_qs.filter( + data["week"] = finished_scrobbles_qs.filter( timestamp__gte=starting_day_of_current_week ).count() - data['month'] = finished_scrobbles_qs.filter( + data["month"] = finished_scrobbles_qs.filter( timestamp__gte=starting_day_of_current_month ).count() - data['year'] = finished_scrobbles_qs.filter( + data["year"] = finished_scrobbles_qs.filter( timestamp__gte=starting_day_of_current_year ).count() - data['alltime'] = finished_scrobbles_qs.count() + data["alltime"] = finished_scrobbles_qs.count() return data def week_of_scrobbles( - user=None, start=None, media: str = 'tracks' + user=None, start=None, media: str = "tracks" ) -> dict[str, int]: now = timezone.now() @@ -62,15 +62,15 @@ def week_of_scrobbles( base_qs = Scrobble.objects.filter(user_filter, played_to_completion=True) media_filter = Q(track__isnull=False) - if media == 'movies': + if media == "movies": media_filter = Q(video__video_type=Video.VideoType.MOVIE) - if media == 'series': + if media == "series": media_filter = Q(video__video_type=Video.VideoType.TV_EPISODE) for day in range(6, -1, -1): start_day = start - timedelta(days=day) end = datetime.combine(start_day, datetime.max.time(), now.tzinfo) - day_of_week = start_day.strftime('%A') + day_of_week = start_day.strftime("%A") scrobble_day_dict[day_of_week] = base_qs.filter( media_filter, @@ -100,14 +100,14 @@ def live_charts( start_day_of_month = now.replace(day=1) start_day_of_year = now.replace(month=1, day=1) - media_model = apps.get_model(app_label='music', model_name=media_type) + media_model = apps.get_model(app_label="music", model_name=media_type) period_queries = { - 'today': {'scrobble__timestamp__gte': start_of_today}, - 'week': {'scrobble__timestamp__gte': start_day_of_week}, - 'month': {'scrobble__timestamp__gte': start_day_of_month}, - 'year': {'scrobble__timestamp__gte': start_day_of_year}, - 'all': {}, + "today": {"scrobble__timestamp__gte": start_of_today}, + "week": {"scrobble__timestamp__gte": start_day_of_week}, + "month": {"scrobble__timestamp__gte": start_day_of_month}, + "year": {"scrobble__timestamp__gte": start_day_of_year}, + "all": {}, } time_filter = Q() diff --git a/vrobbler/apps/music/api/views.py b/vrobbler/apps/music/api/views.py index 788ce00..81906ea 100644 --- a/vrobbler/apps/music/api/views.py +++ b/vrobbler/apps/music/api/views.py @@ -9,18 +9,18 @@ from music.models import Artist, Album, Track class ArtistViewSet(viewsets.ModelViewSet): - queryset = Artist.objects.all().order_by('-created') + queryset = Artist.objects.all().order_by("-created") serializer_class = ArtistSerializer permission_classes = [permissions.IsAuthenticated] class AlbumViewSet(viewsets.ModelViewSet): - queryset = Album.objects.all().order_by('-created') + queryset = Album.objects.all().order_by("-created") serializer_class = AlbumSerializer permission_classes = [permissions.IsAuthenticated] class TrackViewSet(viewsets.ModelViewSet): - queryset = Track.objects.all().order_by('-created') + queryset = Track.objects.all().order_by("-created") serializer_class = TrackSerializer permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/apps/music/apps.py b/vrobbler/apps/music/apps.py index d909c7f..05882f6 100644 --- a/vrobbler/apps/music/apps.py +++ b/vrobbler/apps/music/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class MusicConfig(AppConfig): - name = 'music' + name = "music" diff --git a/vrobbler/apps/music/constants.py b/vrobbler/apps/music/constants.py index b08feb1..4ccfc10 100644 --- a/vrobbler/apps/music/constants.py +++ b/vrobbler/apps/music/constants.py @@ -1,16 +1,16 @@ JELLYFIN_POST_KEYS = { - 'ITEM_TYPE': 'ItemType', - 'RUN_TIME_TICKS': 'RunTimeTicks', - 'RUN_TIME': 'RunTime', - 'TITLE': 'Name', - 'TIMESTAMP': 'UtcTimestamp', - 'YEAR': 'Year', - 'PLAYBACK_POSITION_TICKS': 'PlaybackPositionTicks', - 'PLAYBACK_POSITION': 'PlaybackPosition', - 'ARTIST_MB_ID': 'Provider_musicbrainzartist', - 'ALBUM_MB_ID': 'Provider_musicbrainzalbum', - 'RELEASEGROUP_MB_ID': 'Provider_musicbrainzreleasegroup', - 'TRACK_MB_ID': 'Provider_musicbrainztrack', - 'ALBUM_NAME': 'Album', - 'ARTIST_NAME': 'Artist', + "ITEM_TYPE": "ItemType", + "RUN_TIME_TICKS": "RunTimeTicks", + "RUN_TIME": "RunTime", + "TITLE": "Name", + "TIMESTAMP": "UtcTimestamp", + "YEAR": "Year", + "PLAYBACK_POSITION_TICKS": "PlaybackPositionTicks", + "PLAYBACK_POSITION": "PlaybackPosition", + "ARTIST_MB_ID": "Provider_musicbrainzartist", + "ALBUM_MB_ID": "Provider_musicbrainzalbum", + "RELEASEGROUP_MB_ID": "Provider_musicbrainzreleasegroup", + "TRACK_MB_ID": "Provider_musicbrainztrack", + "ALBUM_NAME": "Album", + "ARTIST_NAME": "Artist", } diff --git a/vrobbler/apps/music/lastfm.py b/vrobbler/apps/music/lastfm.py index 63e4dbc..fb1cf29 100644 --- a/vrobbler/apps/music/lastfm.py +++ b/vrobbler/apps/music/lastfm.py @@ -50,13 +50,13 @@ class LastFM: lastfm_scrobbles = self.get_last_scrobbles(time_from=last_processed) for lfm_scrobble in lastfm_scrobbles: - timestamp = lfm_scrobble.pop('timestamp') + timestamp = lfm_scrobble.pop("timestamp") - artist = get_or_create_artist(lfm_scrobble.pop('artist')) - album = get_or_create_album(lfm_scrobble.pop('album'), artist) + artist = get_or_create_artist(lfm_scrobble.pop("artist")) + album = get_or_create_album(lfm_scrobble.pop("album"), artist) - lfm_scrobble['artist'] = artist - lfm_scrobble['album'] = album + lfm_scrobble["artist"] = artist + lfm_scrobble["album"] = album track = get_or_create_track(**lfm_scrobble) new_scrobble = Scrobble( @@ -85,7 +85,7 @@ class LastFM: created = Scrobble.objects.bulk_create(new_scrobbles) logger.info( f"Created {len(created)} scrobbles", - extra={'created_scrobbles': created}, + extra={"created_scrobbles": created}, ) return created @@ -100,7 +100,7 @@ class LastFM: lfm_params["time_to"] = int(time_to.timestamp()) # if not time_from and not time_to: - lfm_params['limit'] = None + lfm_params["limit"] = None found_scrobbles = self.user.get_recent_tracks(**lfm_params) # TOOD spin this out into a celery task over certain threshold of found scrobbles? diff --git a/vrobbler/apps/music/models.py b/vrobbler/apps/music/models.py index 9f5c03f..9e28bfc 100644 --- a/vrobbler/apps/music/models.py +++ b/vrobbler/apps/music/models.py @@ -28,7 +28,7 @@ class Artist(TimeStampedModel): thumbnail = models.ImageField(upload_to="artist/", **BNULL) class Meta: - unique_together = [['name', 'musicbrainz_id']] + unique_together = [["name", "musicbrainz_id"]] def __str__(self): return self.name @@ -38,27 +38,27 @@ class Artist(TimeStampedModel): return f"https://musicbrainz.org/artist/{self.musicbrainz_id}" def get_absolute_url(self): - return reverse('music:artist_detail', kwargs={'slug': self.uuid}) + return reverse("music:artist_detail", kwargs={"slug": self.uuid}) def scrobbles(self): from scrobbles.models import Scrobble return Scrobble.objects.filter( track__in=self.track_set.all() - ).order_by('-timestamp') + ).order_by("-timestamp") @property def tracks(self): return ( self.track_set.all() - .annotate(scrobble_count=models.Count('scrobble')) - .order_by('-scrobble_count') + .annotate(scrobble_count=models.Count("scrobble")) + .order_by("-scrobble_count") ) def charts(self): from scrobbles.models import ChartRecord - return ChartRecord.objects.filter(track__artist=self).order_by('-year') + return ChartRecord.objects.filter(track__artist=self).order_by("-year") def fix_metadata(self): tadb_info = lookup_artist_from_tadb(self.name) @@ -66,19 +66,19 @@ class Artist(TimeStampedModel): logger.warn(f"No response from TADB for artist {self.name}") return - self.biography = tadb_info['biography'] - self.theaudiodb_genre = tadb_info['genre'] - self.theaudiodb_mood = tadb_info['mood'] + self.biography = tadb_info["biography"] + self.theaudiodb_genre = tadb_info["genre"] + self.theaudiodb_mood = tadb_info["mood"] img_temp = NamedTemporaryFile(delete=True) - img_temp.write(urlopen(tadb_info['thumb_url']).read()) + img_temp.write(urlopen(tadb_info["thumb_url"]).read()) img_temp.flush() img_filename = f"{self.name}_{self.uuid}.jpg" self.thumbnail.save(img_filename, File(img_temp)) @property def rym_link(self): - artist_slug = self.name.lower().replace(' ', '-') + artist_slug = self.name.lower().replace(" ", "-") return f"https://rateyourmusic.com/artist/{artist_slug}/" @property @@ -116,21 +116,21 @@ class Album(TimeStampedModel): return self.name def get_absolute_url(self): - return reverse("music:album_detail", kwargs={'slug': self.uuid}) + return reverse("music:album_detail", kwargs={"slug": self.uuid}) def scrobbles(self): from scrobbles.models import Scrobble return Scrobble.objects.filter( track__in=self.track_set.all() - ).order_by('-timestamp') + ).order_by("-timestamp") @property def tracks(self): return ( self.track_set.all() - .annotate(scrobble_count=models.Count('scrobble')) - .order_by('-scrobble_count') + .annotate(scrobble_count=models.Count("scrobble")) + .order_by("-scrobble_count") ) @property @@ -142,7 +142,7 @@ class Album(TimeStampedModel): if self.primary_artist: artist = self.primary_artist.name album_data = lookup_album_from_tadb(self.name, artist) - if not album_data.get('theaudiodb_id'): + if not album_data.get("theaudiodb_id"): logger.info(f"No data for {self} found in TheAudioDB") return @@ -154,21 +154,21 @@ class Album(TimeStampedModel): or not self.year or not self.musicbrainz_releasegroup_id ): - musicbrainzngs.set_useragent('vrobbler', '0.3.0') + musicbrainzngs.set_useragent("vrobbler", "0.3.0") mb_data = musicbrainzngs.get_release_by_id( - self.musicbrainz_id, includes=['artists', 'release-groups'] + self.musicbrainz_id, includes=["artists", "release-groups"] ) if not self.musicbrainz_releasegroup_id: - self.musicbrainz_releasegroup_id = mb_data['release'][ - 'release-group' - ]['id'] + self.musicbrainz_releasegroup_id = mb_data["release"][ + "release-group" + ]["id"] if not self.musicbrainz_albumartist_id: - self.musicbrainz_albumartist_id = mb_data['release'][ - 'artist-credit' - ][0]['artist']['id'] + self.musicbrainz_albumartist_id = mb_data["release"][ + "artist-credit" + ][0]["artist"]["id"] if not self.year: try: - self.year = mb_data['release']['date'][0:4] + self.year = mb_data["release"]["date"][0:4] except KeyError: pass except IndexError: @@ -176,9 +176,9 @@ class Album(TimeStampedModel): self.save( update_fields=[ - 'musicbrainz_albumartist_id', - 'musicbrainz_releasegroup_id', - 'year', + "musicbrainz_albumartist_id", + "musicbrainz_releasegroup_id", + "year", ] ) @@ -192,7 +192,7 @@ class Album(TimeStampedModel): self.artists.add(t.artist) if ( not self.cover_image - or self.cover_image == 'default-image-replace-me' + or self.cover_image == "default-image-replace-me" ): self.fetch_artwork() self.scrape_theaudiodb() @@ -206,10 +206,10 @@ class Album(TimeStampedModel): ) name = f"{self.name}_{self.uuid}.jpg" self.cover_image = ContentFile(img_data, name=name) - logger.info(f'Setting image to {name}') + logger.info(f"Setting image to {name}") except musicbrainzngs.ResponseError: logger.warning( - f'No cover art found for {self.name} by release' + f"No cover art found for {self.name} by release" ) if ( @@ -222,10 +222,10 @@ class Album(TimeStampedModel): ) name = f"{self.name}_{self.uuid}.jpg" self.cover_image = ContentFile(img_data, name=name) - logger.info(f'Setting image to {name}') + logger.info(f"Setting image to {name}") except musicbrainzngs.ResponseError: logger.warning( - f'No cover art found for {self.name} by release group' + f"No cover art found for {self.name} by release group" ) if not self.cover_image: logger.debug( @@ -257,8 +257,8 @@ class Album(TimeStampedModel): @property def rym_link(self): - artist_slug = self.primary_artist.name.lower().replace(' ', '-') - album_slug = self.name.lower().replace(' ', '-') + artist_slug = self.primary_artist.name.lower().replace(" ", "-") + album_slug = self.name.lower().replace(" ", "-") return f"https://rateyourmusic.com/release/album/{artist_slug}/{album_slug}/" @property @@ -269,25 +269,25 @@ class Album(TimeStampedModel): class Track(ScrobblableMixin): - COMPLETION_PERCENT = getattr(settings, 'MUSIC_COMPLETION_PERCENT', 90) + COMPLETION_PERCENT = getattr(settings, "MUSIC_COMPLETION_PERCENT", 90) class Opinion(models.IntegerChoices): - DOWN = -1, 'Thumbs down' - NEUTRAL = 0, 'No opinion' - UP = 1, 'Thumbs up' + DOWN = -1, "Thumbs down" + NEUTRAL = 0, "No opinion" + UP = 1, "Thumbs up" artist = models.ForeignKey(Artist, on_delete=models.DO_NOTHING) album = models.ForeignKey(Album, on_delete=models.DO_NOTHING, **BNULL) musicbrainz_id = models.CharField(max_length=255, **BNULL) class Meta: - unique_together = [['album', 'musicbrainz_id']] + unique_together = [["album", "musicbrainz_id"]] def __str__(self): return f"{self.title} by {self.artist}" def get_absolute_url(self): - return reverse('music:track_detail', kwargs={'slug': self.uuid}) + return reverse("music:track_detail", kwargs={"slug": self.uuid}) @property def subtitle(self): @@ -310,8 +310,8 @@ class Track(ScrobblableMixin): exist. """ - if not artist_dict.get('name') or not artist_dict.get( - 'musicbrainz_id' + if not artist_dict.get("name") or not artist_dict.get( + "musicbrainz_id" ): logger.warning( f"No artist or artist musicbrainz ID found in message from source, not scrobbling" @@ -325,8 +325,8 @@ class Track(ScrobblableMixin): if not album.cover_image: album.fetch_artwork() - track_dict['album_id'] = getattr(album, "id", None) - track_dict['artist_id'] = artist.id + track_dict["album_id"] = getattr(album, "id", None) + track_dict["artist_id"] = artist.id track, created = cls.objects.get_or_create(**track_dict) diff --git a/vrobbler/apps/music/musicbrainz.py b/vrobbler/apps/music/musicbrainz.py index 9cb9a86..00b706c 100644 --- a/vrobbler/apps/music/musicbrainz.py +++ b/vrobbler/apps/music/musicbrainz.py @@ -9,40 +9,40 @@ logger = logging.getLogger(__name__) def lookup_album_from_mb(musicbrainz_id: str) -> dict: release_dict = {} - musicbrainzngs.set_useragent('vrobbler', '0.3.0') + musicbrainzngs.set_useragent("vrobbler", "0.3.0") release_data = musicbrainzngs.get_release_by_id( musicbrainz_id, - includes=['artists', 'release-groups', 'recordings'], - ).get('release') + includes=["artists", "release-groups", "recordings"], + ).get("release") if not release_data: return release_dict - primary_artist = release_data.get('artist-credit')[0] + primary_artist = release_data.get("artist-credit")[0] release_dict = { - 'artist': { - 'name': primary_artist.get('name'), - 'musicbrainz_id': primary_artist.get('id'), + "artist": { + "name": primary_artist.get("name"), + "musicbrainz_id": primary_artist.get("id"), }, - 'album': { - 'name': release_data.get('title'), - 'musicbrainz_id': musicbrainz_id, - 'musicbrainz_releasegroup_id': release_data.get( - 'release-group' - ).get('id'), - 'musicbrainz_albumaritist_id': primary_artist.get('id'), - 'year': release_data.get('year')[0:4], + "album": { + "name": release_data.get("title"), + "musicbrainz_id": musicbrainz_id, + "musicbrainz_releasegroup_id": release_data.get( + "release-group" + ).get("id"), + "musicbrainz_albumaritist_id": primary_artist.get("id"), + "year": release_data.get("year")[0:4], }, } - release_dict['tracks'] = [] - for track in release_data.get('medium-list')[0]['track-list']: - recording = track['recording'] - release_dict['tracks'].append( + release_dict["tracks"] = [] + for track in release_data.get("medium-list")[0]["track-list"]: + recording = track["recording"] + release_dict["tracks"].append( { - 'title': recording['title'], - 'musicbrainz_id': recording['id'], - 'run_time_ticks': track['length'], + "title": recording["title"], + "musicbrainz_id": recording["id"], + "run_time_ticks": track["length"], } ) @@ -50,12 +50,12 @@ def lookup_album_from_mb(musicbrainz_id: str) -> dict: def lookup_album_dict_from_mb(release_name: str, artist_name: str) -> dict: - musicbrainzngs.set_useragent('vrobbler', '0.3.0') + musicbrainzngs.set_useragent("vrobbler", "0.3.0") top_result = musicbrainzngs.search_releases( release_name, artist=artist_name - )['release-list'][0] - score = int(top_result.get('ext:score')) + )["release-list"][0] + score = int(top_result.get("ext:score")) if score < 85: logger.debug( "Album lookup score below 85 threshold", @@ -74,12 +74,12 @@ def lookup_album_dict_from_mb(release_name: str, artist_name: str) -> dict: def lookup_artist_from_mb(artist_name: str) -> str: - musicbrainzngs.set_useragent('vrobbler', '0.3.0') + musicbrainzngs.set_useragent("vrobbler", "0.3.0") top_result = musicbrainzngs.search_artists(artist=artist_name)[ - 'artist-list' + "artist-list" ][0] - score = int(top_result.get('ext:score')) + score = int(top_result.get("ext:score")) if score < 85: logger.debug( "Artist lookup score below 85 threshold", @@ -93,12 +93,12 @@ def lookup_artist_from_mb(artist_name: str) -> str: def lookup_track_from_mb( track_name: str, artist_mbid: str, album_mbid: str ) -> str: - musicbrainzngs.set_useragent('vrobbler', '0.3.0') + musicbrainzngs.set_useragent("vrobbler", "0.3.0") top_result = musicbrainzngs.search_recordings( query=track_name, artist=artist_mbid, release=album_mbid - )['recording-list'][0] - score = int(top_result.get('ext:score')) + )["recording-list"][0] + score = int(top_result.get("ext:score")) if score < 85: logger.debug( "Track lookup score below 85 threshold", diff --git a/vrobbler/apps/music/theaudiodb.py b/vrobbler/apps/music/theaudiodb.py index 67c91b5..598ff2e 100644 --- a/vrobbler/apps/music/theaudiodb.py +++ b/vrobbler/apps/music/theaudiodb.py @@ -26,13 +26,13 @@ def lookup_artist_from_tadb(name: str) -> dict: return {} results = json.loads(response.content) - if results['artists']: - artist = results['artists'][0] + if results["artists"]: + artist = results["artists"][0] - artist_info['biography'] = artist.get('strBiographyEN') - artist_info['genre'] = artist.get('strGenre') - artist_info['mood'] = artist.get('strMood') - artist_info['thumb_url'] = artist.get('strArtistThumb') + artist_info["biography"] = artist.get("strBiographyEN") + artist_info["genre"] = artist.get("strGenre") + artist_info["mood"] = artist.get("strMood") + artist_info["thumb_url"] = artist.get("strArtistThumb") return artist_info @@ -41,7 +41,7 @@ def lookup_album_from_tadb(name: str, artist: str) -> dict: album_info = {} artist = urllib.parse.quote(artist) name = urllib.parse.quote(name) - response = requests.get(''.join([ALBUM_SEARCH_URL, artist, "&a=", name])) + response = requests.get("".join([ALBUM_SEARCH_URL, artist, "&a=", name])) if response.status_code != 200: logger.warn(f"Bad response from TADB: {response.status_code}") @@ -52,31 +52,31 @@ def lookup_album_from_tadb(name: str, artist: str) -> dict: return {} results = json.loads(response.content) - if results['album']: - album = results['album'][0] + if results["album"]: + album = results["album"][0] - album_info['theaudiodb_id'] = album.get('idAlbum') - album_info['theaudiodb_description'] = album.get('strDescriptionEN') - album_info['theaudiodb_genre'] = album.get('strGenre') - album_info['theaudiodb_style'] = album.get('strStyle') - album_info['theaudiodb_mood'] = album.get('strMood') - album_info['theaudiodb_speed'] = album.get('strSpeed') - album_info['theaudiodb_theme'] = album.get('strTheme') - album_info['allmusic_id'] = album.get('strAllMusicID') - album_info['wikipedia_slug'] = album.get('strWikipediaID') - album_info['discogs_id'] = album.get('strDiscogsID') - album_info['wikidata_id'] = album.get('strWikidataID') - album_info['rateyourmusic_id'] = album.get('strRateYourMusicID') + album_info["theaudiodb_id"] = album.get("idAlbum") + album_info["theaudiodb_description"] = album.get("strDescriptionEN") + album_info["theaudiodb_genre"] = album.get("strGenre") + album_info["theaudiodb_style"] = album.get("strStyle") + album_info["theaudiodb_mood"] = album.get("strMood") + album_info["theaudiodb_speed"] = album.get("strSpeed") + album_info["theaudiodb_theme"] = album.get("strTheme") + album_info["allmusic_id"] = album.get("strAllMusicID") + album_info["wikipedia_slug"] = album.get("strWikipediaID") + album_info["discogs_id"] = album.get("strDiscogsID") + album_info["wikidata_id"] = album.get("strWikidataID") + album_info["rateyourmusic_id"] = album.get("strRateYourMusicID") - if album.get('intYearReleased'): - album_info['theaudiodb_year_released'] = float( - album.get('intYearReleased') + if album.get("intYearReleased"): + album_info["theaudiodb_year_released"] = float( + album.get("intYearReleased") ) - if album.get('intScore'): - album_info['theaudiodb_score'] = float(album.get('intScore')) - if album.get('intScoreVotes'): - album_info['theaudiodb_score_votes'] = int( - album.get('intScoreVotes') + if album.get("intScore"): + album_info["theaudiodb_score"] = float(album.get("intScore")) + if album.get("intScoreVotes"): + album_info["theaudiodb_score_votes"] = int( + album.get("intScoreVotes") ) return album_info diff --git a/vrobbler/apps/music/urls.py b/vrobbler/apps/music/urls.py index bf7e48f..c8a6b1c 100644 --- a/vrobbler/apps/music/urls.py +++ b/vrobbler/apps/music/urls.py @@ -1,26 +1,26 @@ from django.urls import path from music import views -app_name = 'music' +app_name = "music" urlpatterns = [ - path('albums/', views.AlbumListView.as_view(), name='albums_list'), + path("albums/", views.AlbumListView.as_view(), name="albums_list"), path( - 'album//', + "album//", views.AlbumDetailView.as_view(), - name='album_detail', + name="album_detail", ), - path("tracks/", views.TrackListView.as_view(), name='tracks_list'), + path("tracks/", views.TrackListView.as_view(), name="tracks_list"), path( - 'tracks//', + "tracks//", views.TrackDetailView.as_view(), - name='track_detail', + name="track_detail", ), - path('artists/', views.ArtistListView.as_view(), name='artist_list'), + path("artists/", views.ArtistListView.as_view(), name="artist_list"), path( - 'artists//', + "artists//", views.ArtistDetailView.as_view(), - name='artist_detail', + name="artist_detail", ), ] diff --git a/vrobbler/apps/music/utils.py b/vrobbler/apps/music/utils.py index bafd2ad..2c89c4c 100644 --- a/vrobbler/apps/music/utils.py +++ b/vrobbler/apps/music/utils.py @@ -17,19 +17,19 @@ from music.models import Album, Artist, Track def get_or_create_artist(name: str, mbid: str = None) -> Artist: artist = None - logger.debug(f'Got artist {name} and mbid: {mbid}') + logger.debug(f"Got artist {name} and mbid: {mbid}") - if 'feat.' in name.lower(): + if "feat." in name.lower(): name = re.split("feat.", name, flags=re.IGNORECASE)[0].strip() - if 'featuring' in name.lower(): + if "featuring" in name.lower(): name = re.split("featuring", name, flags=re.IGNORECASE)[0].strip() - if '&' in name.lower(): + if "&" in name.lower(): name = re.split("&", name, flags=re.IGNORECASE)[0].strip() artist_dict = lookup_artist_from_mb(name) - mbid = mbid or artist_dict['id'] + mbid = mbid or artist_dict["id"] - logger.debug(f'Looking up artist {name} and mbid: {mbid}') + logger.debug(f"Looking up artist {name} and mbid: {mbid}") artist = Artist.objects.filter(musicbrainz_id=mbid).first() if not artist: artist = Artist.objects.create(name=name, musicbrainz_id=mbid) @@ -44,9 +44,9 @@ def get_or_create_artist(name: str, mbid: str = None) -> Artist: def get_or_create_album(name: str, artist: Artist, mbid: str = None) -> Album: album = None album_dict = lookup_album_dict_from_mb(name, artist_name=artist.name) - mbid = mbid or album_dict['mb_id'] + mbid = mbid or album_dict["mb_id"] - logger.debug(f'Looking up album {name} and mbid: {mbid}') + logger.debug(f"Looking up album {name} and mbid: {mbid}") album = Album.objects.filter(musicbrainz_id=mbid).first() if not album: @@ -81,7 +81,7 @@ def get_or_create_track( title, artist.musicbrainz_id, album.musicbrainz_id, - )['id'] + )["id"] track = Track.objects.filter(musicbrainz_id=mbid).first() diff --git a/vrobbler/apps/music/views.py b/vrobbler/apps/music/views.py index 6b0885f..fd1617c 100644 --- a/vrobbler/apps/music/views.py +++ b/vrobbler/apps/music/views.py @@ -17,11 +17,11 @@ class TrackListView(generic.ListView): class TrackDetailView(generic.DetailView): model = Track - slug_field = 'uuid' + slug_field = "uuid" def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) - context_data['charts'] = ChartRecord.objects.filter( + context_data["charts"] = ChartRecord.objects.filter( track=self.object, rank__in=[1, 2, 3] ) return context_data @@ -35,7 +35,7 @@ class ArtistListView(generic.ListView): return ( super() .get_queryset() - .annotate(scrobble_count=Count('track__scrobble')) + .annotate(scrobble_count=Count("track__scrobble")) .order_by("-scrobble_count") ) @@ -43,17 +43,17 @@ class ArtistListView(generic.ListView): context_data = super().get_context_data( object_list=object_list, **kwargs ) - context_data['view'] = self.request.GET.get('view') + context_data["view"] = self.request.GET.get("view") return context_data class ArtistDetailView(generic.DetailView): model = Artist - slug_field = 'uuid' + slug_field = "uuid" def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) - artist = context_data['object'] + artist = context_data["object"] rank = 1 tracks_ranked = [] scrobbles = artist.tracks.first().scrobble_count @@ -63,8 +63,8 @@ class ArtistDetailView(generic.DetailView): tracks_ranked.append((rank, track)) scrobbles = track.scrobble_count - context_data['tracks_ranked'] = tracks_ranked - context_data['charts'] = ChartRecord.objects.filter( + context_data["tracks_ranked"] = tracks_ranked + context_data["charts"] = ChartRecord.objects.filter( artist=self.object, rank__in=[1, 2, 3] ) return context_data @@ -77,14 +77,14 @@ class AlbumListView(generic.ListView): return ( super() .get_queryset() - .annotate(scrobble_count=Count('track__scrobble')) + .annotate(scrobble_count=Count("track__scrobble")) .order_by("-scrobble_count") ) class AlbumDetailView(generic.DetailView): model = Album - slug_field = 'uuid' + slug_field = "uuid" def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) diff --git a/vrobbler/apps/podcasts/apps.py b/vrobbler/apps/podcasts/apps.py index f6e2f1e..a48a08b 100644 --- a/vrobbler/apps/podcasts/apps.py +++ b/vrobbler/apps/podcasts/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class PodcastsConfig(AppConfig): - name = 'podcasts' + name = "podcasts" diff --git a/vrobbler/apps/podcasts/models.py b/vrobbler/apps/podcasts/models.py index 31999a8..c49b267 100644 --- a/vrobbler/apps/podcasts/models.py +++ b/vrobbler/apps/podcasts/models.py @@ -35,7 +35,7 @@ class Podcast(TimeStampedModel): class Episode(ScrobblableMixin): - COMPLETION_PERCENT = getattr(settings, 'PODCAST_COMPLETION_PERCENT', 90) + COMPLETION_PERCENT = getattr(settings, "PODCAST_COMPLETION_PERCENT", 90) podcast = models.ForeignKey(Podcast, on_delete=models.DO_NOTHING) number = models.IntegerField(**BNULL) @@ -61,12 +61,12 @@ class Episode(ScrobblableMixin): producer before saving the epsiode so it can be scrobbled. """ - if not podcast_dict.get('name'): + if not podcast_dict.get("name"): logger.warning(f"No name from source for podcast, not scrobbling") return producer = None - if producer_dict.get('name'): + if producer_dict.get("name"): producer, producer_created = Producer.objects.get_or_create( **producer_dict ) @@ -85,7 +85,7 @@ class Episode(ScrobblableMixin): else: logger.debug(f"Found podcast {podcast}") - episode_dict['podcast_id'] = podcast.id + episode_dict["podcast_id"] = podcast.id episode, created = cls.objects.get_or_create(**episode_dict) if created: diff --git a/vrobbler/apps/profiles/api/serializers.py b/vrobbler/apps/profiles/api/serializers.py index 5e233b7..340c89e 100644 --- a/vrobbler/apps/profiles/api/serializers.py +++ b/vrobbler/apps/profiles/api/serializers.py @@ -9,10 +9,10 @@ from profiles.models import UserProfile class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User - exclude = ('password',) + exclude = ("password",) class UserProfileSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = UserProfile - exclude = ('lastfm_password',) + exclude = ("lastfm_password",) diff --git a/vrobbler/apps/profiles/api/views.py b/vrobbler/apps/profiles/api/views.py index 9779c72..e452890 100644 --- a/vrobbler/apps/profiles/api/views.py +++ b/vrobbler/apps/profiles/api/views.py @@ -13,7 +13,7 @@ class UserViewSet(viewsets.ModelViewSet): API endpoint that allows users to be viewed or edited. """ - queryset = User.objects.all().order_by('-date_joined') + queryset = User.objects.all().order_by("-date_joined") serializer_class = UserSerializer permission_classes = [permissions.IsAuthenticated] @@ -23,6 +23,6 @@ class UserProfileViewSet(viewsets.ModelViewSet): API endpoint that allows users to be viewed or edited. """ - queryset = UserProfile.objects.all().order_by('-created') + queryset = UserProfile.objects.all().order_by("-created") serializer_class = UserProfileSerializer permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/apps/scrobbles/admin.py b/vrobbler/apps/scrobbles/admin.py index 62d454e..5b4cfa5 100644 --- a/vrobbler/apps/scrobbles/admin.py +++ b/vrobbler/apps/scrobbles/admin.py @@ -11,8 +11,8 @@ from scrobbles.models import ( class ScrobbleInline(admin.TabularInline): model = Scrobble extra = 0 - raw_id_fields = ('video', 'podcast_episode', 'track') - exclude = ('source_id', 'scrobble_log') + raw_id_fields = ("video", "podcast_episode", "track") + exclude = ("source_id", "scrobble_log") class ImportBaseAdmin(admin.ModelAdmin): @@ -81,11 +81,11 @@ class ScrobbleAdmin(admin.ModelAdmin): "played_to_completion", ) raw_id_fields = ( - 'video', - 'podcast_episode', - 'track', - 'sport_event', - 'book', + "video", + "podcast_episode", + "track", + "sport_event", + "book", ) list_filter = ("is_paused", "in_progress", "source", "track__artist") ordering = ("-timestamp",) diff --git a/vrobbler/apps/scrobbles/api/views.py b/vrobbler/apps/scrobbles/api/views.py index 3768252..c89b549 100644 --- a/vrobbler/apps/scrobbles/api/views.py +++ b/vrobbler/apps/scrobbles/api/views.py @@ -14,7 +14,7 @@ from scrobbles.models import ( class ScrobbleViewSet(viewsets.ModelViewSet): - queryset = Scrobble.objects.all().order_by('-timestamp') + queryset = Scrobble.objects.all().order_by("-timestamp") serializer_class = ScrobbleSerializer permission_classes = [permissions.IsAuthenticated] @@ -23,7 +23,7 @@ class ScrobbleViewSet(viewsets.ModelViewSet): class KoReaderImportViewSet(viewsets.ModelViewSet): - queryset = KoReaderImport.objects.all().order_by('-created') + queryset = KoReaderImport.objects.all().order_by("-created") serializer_class = KoReaderImportSerializer permission_classes = [permissions.IsAuthenticated] @@ -32,7 +32,7 @@ class KoReaderImportViewSet(viewsets.ModelViewSet): class AudioScrobblerTSVImportViewSet(viewsets.ModelViewSet): - queryset = AudioScrobblerTSVImport.objects.all().order_by('-created') + queryset = AudioScrobblerTSVImport.objects.all().order_by("-created") serializer_class = AudioScrobblerTSVImportSerializer permission_classes = [permissions.IsAuthenticated] @@ -41,7 +41,7 @@ class AudioScrobblerTSVImportViewSet(viewsets.ModelViewSet): class LastFmImportViewSet(viewsets.ModelViewSet): - queryset = LastFmImport.objects.all().order_by('-created') + queryset = LastFmImport.objects.all().order_by("-created") serializer_class = LastFmImportSerializer permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/apps/scrobbles/apps.py b/vrobbler/apps/scrobbles/apps.py index 9a61964..3396fa8 100644 --- a/vrobbler/apps/scrobbles/apps.py +++ b/vrobbler/apps/scrobbles/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class ScrobblesConfig(AppConfig): - name = 'scrobbles' + name = "scrobbles" diff --git a/vrobbler/apps/scrobbles/context_processors.py b/vrobbler/apps/scrobbles/context_processors.py index 19358b1..86dba18 100644 --- a/vrobbler/apps/scrobbles/context_processors.py +++ b/vrobbler/apps/scrobbles/context_processors.py @@ -9,7 +9,7 @@ def now_playing(request): if not user.is_authenticated: return {} return { - 'now_playing_list': Scrobble.objects.filter( + "now_playing_list": Scrobble.objects.filter( in_progress=True, is_paused=False, user=user, diff --git a/vrobbler/apps/scrobbles/export.py b/vrobbler/apps/scrobbles/export.py index f27f34b..05116e1 100644 --- a/vrobbler/apps/scrobbles/export.py +++ b/vrobbler/apps/scrobbles/export.py @@ -17,19 +17,19 @@ def export_scrobbles(start_date=None, end_date=None, format="AS"): start_query, end_query, track__isnull=False ) headers = [] - extension = 'tsv' - delimiter = '\t' + extension = "tsv" + delimiter = "\t" if format == "as": headers = [ - ['#AUDIOSCROBBLER/1.1'], - ['#TZ/UTC'], - ['#CLIENT/Vrobbler 1.0.0'], + ["#AUDIOSCROBBLER/1.1"], + ["#TZ/UTC"], + ["#CLIENT/Vrobbler 1.0.0"], ] if format == "csv": - delimiter = ',' - extension = 'csv' + delimiter = "," + extension = "csv" headers = [ [ "artists", @@ -43,7 +43,7 @@ def export_scrobbles(start_date=None, end_date=None, format="AS"): ] ] - with tempfile.NamedTemporaryFile(mode='w', delete=False) as outfile: + with tempfile.NamedTemporaryFile(mode="w", delete=False) as outfile: writer = csv.writer(outfile, delimiter=delimiter) for row in headers: writer.writerow(row) @@ -60,7 +60,7 @@ def export_scrobbles(start_date=None, end_date=None, format="AS"): track_number, track.run_time, track_rating, - scrobble.timestamp.strftime('%s'), + scrobble.timestamp.strftime("%s"), track.musicbrainz_id, ] writer.writerow(row) diff --git a/vrobbler/apps/scrobbles/forms.py b/vrobbler/apps/scrobbles/forms.py index 40c2c39..70ffa52 100644 --- a/vrobbler/apps/scrobbles/forms.py +++ b/vrobbler/apps/scrobbles/forms.py @@ -5,9 +5,9 @@ class ExportScrobbleForm(forms.Form): """Provide options for downloading scrobbles""" EXPORT_TYPES = ( - ('as', 'Audioscrobbler'), - ('csv', 'CSV'), - ('html', 'HTML'), + ("as", "Audioscrobbler"), + ("csv", "CSV"), + ("html", "HTML"), ) export_type = forms.ChoiceField(choices=EXPORT_TYPES) @@ -17,9 +17,9 @@ class ScrobbleForm(forms.Form): label="", widget=forms.TextInput( attrs={ - 'class': "form-control form-control-dark w-100", - 'placeholder': "Scrobble something (IMDB ID, String, TVDB ID ...)", - 'aria-label': "Scrobble something", + "class": "form-control form-control-dark w-100", + "placeholder": "Scrobble something (IMDB ID, String, TVDB ID ...)", + "aria-label": "Scrobble something", } ), ) diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index a558ae1..02b8fbf 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -49,17 +49,17 @@ class BaseFileImportMixin(TimeStampedModel): def human_start(self): start = "Unknown" if self.processing_started: - start = self.processing_started.strftime('%B %d, %Y at %H:%M') + start = self.processing_started.strftime("%B %d, %Y at %H:%M") return start @property def import_type(self) -> str: class_name = self.__class__.__name__ - if class_name == 'AudioscrobblerTSVImport': + if class_name == "AudioscrobblerTSVImport": return "Audioscrobbler" - if class_name == 'KoReaderImport': + if class_name == "KoReaderImport": return "KoReader" - if self.__class__.__name__ == 'LastFMImport': + if self.__class__.__name__ == "LastFMImport": return "LastFM" return "Generic" @@ -75,7 +75,7 @@ class BaseFileImportMixin(TimeStampedModel): logger.warning("No lines in process log found to undo") return - for line in self.process_log.split('\n'): + for line in self.process_log.split("\n"): scrobble_id = line.split("\t")[0] scrobble = Scrobble.objects.filter(id=scrobble_id).first() if not scrobble: @@ -105,7 +105,7 @@ class BaseFileImportMixin(TimeStampedModel): def mark_finished(self): self.processed_finished = timezone.now() - self.save(update_fields=['processed_finished']) + self.save(update_fields=["processed_finished"]) def record_log(self, scrobbles): self.process_log = "" @@ -133,13 +133,13 @@ class KoReaderImport(BaseFileImportMixin): def get_absolute_url(self): return reverse( - 'scrobbles:koreader-import-detail', kwargs={'slug': self.uuid} + "scrobbles:koreader-import-detail", kwargs={"slug": self.uuid} ) def get_path(instance, filename): - extension = filename.split('.')[-1] + extension = filename.split(".")[-1] uuid = instance.uuid - return f'koreader-uploads/{uuid}.{extension}' + return f"koreader-uploads/{uuid}.{extension}" sqlite_file = models.FileField(upload_to=get_path, **BNULL) @@ -169,13 +169,13 @@ class AudioScrobblerTSVImport(BaseFileImportMixin): def get_absolute_url(self): return reverse( - 'scrobbles:tsv-import-detail', kwargs={'slug': self.uuid} + "scrobbles:tsv-import-detail", kwargs={"slug": self.uuid} ) def get_path(instance, filename): - extension = filename.split('.')[-1] + extension = filename.split(".")[-1] uuid = instance.uuid - return f'audioscrobbler-uploads/{uuid}.{extension}' + return f"audioscrobbler-uploads/{uuid}.{extension}" tsv_file = models.FileField(upload_to=get_path, **BNULL) @@ -209,7 +209,7 @@ class LastFmImport(BaseFileImportMixin): def get_absolute_url(self): return reverse( - 'scrobbles:lastfm-import-detail', kwargs={'slug': self.uuid} + "scrobbles:lastfm-import-detail", kwargs={"slug": self.uuid} ) def process(self, import_all=False): @@ -332,13 +332,13 @@ class ChartRecord(TimeStampedModel): @property def period_type(self) -> str: - period = 'year' + period = "year" if self.month: - period = 'month' + period = "month" if self.week: - period = 'week' + period = "week" if self.day: - period = 'day' + period = "day" return period def __str__(self): @@ -357,7 +357,7 @@ class ChartRecord(TimeStampedModel): get_params = get_params = get_params + f"-{self.day}" if self.artist: get_params = get_params + "&media=Artist" - return reverse('scrobbles:charts-home') + get_params + return reverse("scrobbles:charts-home") + get_params @classmethod def build(cls, user, **kwargs): @@ -424,12 +424,12 @@ class Scrobble(TimeStampedModel): @property def status(self) -> str: if self.is_paused: - return 'paused' + return "paused" if self.played_to_completion: - return 'finished' + return "finished" if self.in_progress: - return 'in-progress' - return 'zombie' + return "in-progress" + return "zombie" @property def is_stale(self) -> bool: @@ -488,7 +488,7 @@ class Scrobble(TimeStampedModel): return media_obj def __str__(self): - timestamp = self.timestamp.strftime('%Y-%m-%d') + timestamp = self.timestamp.strftime("%Y-%m-%d") return f"Scrobble of {self.media_obj} ({timestamp})" @classmethod @@ -496,28 +496,28 @@ class Scrobble(TimeStampedModel): cls, media, user_id: int, scrobble_data: dict ) -> "Scrobble": - if media.__class__.__name__ == 'Track': + if media.__class__.__name__ == "Track": media_query = models.Q(track=media) - scrobble_data['track_id'] = media.id - if media.__class__.__name__ == 'Video': + scrobble_data["track_id"] = media.id + if media.__class__.__name__ == "Video": media_query = models.Q(video=media) - scrobble_data['video_id'] = media.id - if media.__class__.__name__ == 'Episode': + scrobble_data["video_id"] = media.id + if media.__class__.__name__ == "Episode": media_query = models.Q(podcast_episode=media) - scrobble_data['podcast_episode_id'] = media.id - if media.__class__.__name__ == 'SportEvent': + scrobble_data["podcast_episode_id"] = media.id + if media.__class__.__name__ == "SportEvent": media_query = models.Q(sport_event=media) - scrobble_data['sport_event_id'] = media.id - if media.__class__.__name__ == 'Book': + scrobble_data["sport_event_id"] = media.id + if media.__class__.__name__ == "Book": media_query = models.Q(book=media) - scrobble_data['book_id'] = media.id + scrobble_data["book_id"] = media.id scrobble = ( cls.objects.filter( media_query, user_id=user_id, ) - .order_by('-modified') + .order_by("-modified") .first() ) if scrobble and scrobble.can_be_updated: @@ -527,21 +527,21 @@ class Scrobble(TimeStampedModel): ) return scrobble.update(scrobble_data) - source = scrobble_data['source'] + source = scrobble_data["source"] logger.info( f"Creating for {media.id} - {source}", {"scrobble_data": scrobble_data, "media": media}, ) # If creating a new scrobble, we don't need status - scrobble_data.pop('mopidy_status', None) - scrobble_data.pop('jellyfin_status', None) + scrobble_data.pop("mopidy_status", None) + scrobble_data.pop("jellyfin_status", None) return cls.create(scrobble_data) def update(self, scrobble_data: dict) -> "Scrobble": # Status is a field we get from Mopidy, which refuses to poll us - scrobble_status = scrobble_data.pop('mopidy_status', None) + scrobble_status = scrobble_data.pop("mopidy_status", None) if not scrobble_status: - scrobble_status = scrobble_data.pop('jellyfin_status', None) + scrobble_status = scrobble_data.pop("jellyfin_status", None) if self.percent_played < 100: # Only worry about ticks if we haven't gotten to the end @@ -566,7 +566,7 @@ class Scrobble(TimeStampedModel): cls, scrobble_data: dict, ) -> "Scrobble": - scrobble_data['scrobble_log'] = "" + scrobble_data["scrobble_log"] = "" scrobble = cls.objects.create( **scrobble_data, ) @@ -576,7 +576,7 @@ class Scrobble(TimeStampedModel): if not self.in_progress: return self.in_progress = False - self.save(update_fields=['in_progress']) + self.save(update_fields=["in_progress"]) logger.info(f"{self.id} - {self.source}") check_scrobble_for_finish(self, force_finish) @@ -607,5 +607,5 @@ class Scrobble(TimeStampedModel): f"{self.id} - {self.playback_position_ticks} - {self.source}" ) self.save( - update_fields=['playback_position_ticks', 'playback_position'] + update_fields=["playback_position_ticks", "playback_position"] ) diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index 3d39640..c935306 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -98,7 +98,7 @@ def build_scrobble_dict(data_dict: dict, user_id: int) -> dict: jellyfin_status = "resumed" if data_dict.get("IsPaused"): jellyfin_status = "paused" - elif data_dict.get("NotificationType") == 'PlaybackStop': + elif data_dict.get("NotificationType") == "PlaybackStop": jellyfin_status = "stopped" playback_ticks = data_dict.get("PlaybackPositionTicks", "") @@ -111,7 +111,7 @@ def build_scrobble_dict(data_dict: dict, user_id: int) -> dict: "playback_position_ticks": playback_ticks, "playback_position": data_dict.get("PlaybackPosition", ""), "source": data_dict.get("ClientName", "Vrobbler"), - "source_id": data_dict.get('MediaSourceId'), + "source_id": data_dict.get("MediaSourceId"), "jellyfin_status": jellyfin_status, } @@ -137,7 +137,7 @@ def jellyfin_scrobble_track( album = get_or_create_album( data_dict.get(JELLYFIN_POST_KEYS["ALBUM_NAME"]), artist=artist, - mbid=data_dict.get(JELLYFIN_POST_KEYS['ALBUM_MB_ID']), + mbid=data_dict.get(JELLYFIN_POST_KEYS["ALBUM_MB_ID"]), ) run_time_ticks = ( diff --git a/vrobbler/apps/scrobbles/stats.py b/vrobbler/apps/scrobbles/stats.py index 6660700..b731561 100644 --- a/vrobbler/apps/scrobbles/stats.py +++ b/vrobbler/apps/scrobbles/stats.py @@ -38,14 +38,14 @@ def get_scrobble_count_qs( tz = pytz.timezone(user.profile.timezone) tz = pytz.utc - data_model = apps.get_model(app_label='music', model_name='Track') + data_model = apps.get_model(app_label="music", model_name="Track") if model_str == "Artist": - data_model = apps.get_model(app_label='music', model_name='Artist') + data_model = apps.get_model(app_label="music", model_name="Artist") if model_str == "Video": - data_model = apps.get_model(app_label='videos', model_name='Video') + data_model = apps.get_model(app_label="videos", model_name="Video") if model_str == "SportEvent": data_model = apps.get_model( - app_label='sports', model_name='SportEvent' + app_label="sports", model_name="SportEvent" ) if model_str == "Artist": @@ -69,14 +69,14 @@ def get_scrobble_count_qs( end = datetime(year, 12, 31, tzinfo=tz) if year and day and month: - logger.debug('Filtering by year, month and day') + logger.debug("Filtering by year, month and day") start = datetime(year, month, day, 0, 0, tzinfo=tz) end = datetime(year, month, day, 23, 59, tzinfo=tz) elif year and week: - logger.debug('Filtering by year and week') + logger.debug("Filtering by year and week") start, end = get_start_end_dates_by_week(year, week, tz) elif month: - logger.debug('Filtering by month') + logger.debug("Filtering by month") end_day = calendar.monthrange(year, month)[1] start = datetime(year, month, 1, tzinfo=tz) end = datetime(year, month, end_day, tzinfo=tz) @@ -113,7 +113,7 @@ def build_charts( model_str="Track", ): ChartRecord = apps.get_model( - app_label='scrobbles', model_name='ChartRecord' + app_label="scrobbles", model_name="ChartRecord" ) results = get_scrobble_count_qs(year, month, week, day, user, model_str) unique_counts = list(set([result.scrobble_count for result in results])) @@ -125,20 +125,20 @@ def build_charts( chart_records = [] for result in results: chart_record = { - 'year': year, - 'week': week, - 'month': month, - 'day': day, - 'user': user, + "year": year, + "week": week, + "month": month, + "day": day, + "user": user, } - chart_record['rank'] = ranks[result.scrobble_count] - chart_record['count'] = result.scrobble_count - if model_str == 'Track': - chart_record['track'] = result - if model_str == 'Video': - chart_record['video'] = result - if model_str == 'Artist': - chart_record['artist'] = result + chart_record["rank"] = ranks[result.scrobble_count] + chart_record["count"] = result.scrobble_count + if model_str == "Track": + chart_record["track"] = result + if model_str == "Video": + chart_record["video"] = result + if model_str == "Artist": + chart_record["artist"] = result chart_records.append(ChartRecord(**chart_record)) ChartRecord.objects.bulk_create( chart_records, ignore_conflicts=True, batch_size=500 @@ -148,7 +148,7 @@ def build_charts( def build_yesterdays_charts_for_user(user: "User", model_str="Track") -> None: """Given a user calculate needed charts.""" ChartRecord = apps.get_model( - app_label='scrobbles', model_name='ChartRecord' + app_label="scrobbles", model_name="ChartRecord" ) tz = pytz.timezone(settings.TIME_ZONE) if user and user.is_authenticated: @@ -199,9 +199,9 @@ def build_yesterdays_charts_for_user(user: "User", model_str="Track") -> None: def build_missing_charts_for_user(user: "User", model_str="Track") -> None: """""" ChartRecord = apps.get_model( - app_label='scrobbles', model_name='ChartRecord' + app_label="scrobbles", model_name="ChartRecord" ) - Scrobble = apps.get_model(app_label='scrobbles', model_name='Scrobble') + Scrobble = apps.get_model(app_label="scrobbles", model_name="Scrobble") logger.info(f"Generating historical charts for {user}") tz = pytz.timezone(settings.TIME_ZONE) @@ -211,7 +211,7 @@ def build_missing_charts_for_user(user: "User", model_str="Track") -> None: first_scrobble = ( Scrobble.objects.filter(user=user, played_to_completion=True) - .order_by('created') + .order_by("created") .first() ) diff --git a/vrobbler/apps/scrobbles/templatetags/urlreplace.py b/vrobbler/apps/scrobbles/templatetags/urlreplace.py index 36cb652..c6083df 100644 --- a/vrobbler/apps/scrobbles/templatetags/urlreplace.py +++ b/vrobbler/apps/scrobbles/templatetags/urlreplace.py @@ -5,6 +5,6 @@ register = template.Library() @register.simple_tag(takes_context=True) def urlreplace(context, **kwargs): - query = context['request'].GET.copy() + query = context["request"].GET.copy() query.update(kwargs) return query.urlencode() diff --git a/vrobbler/apps/scrobbles/tsv.py b/vrobbler/apps/scrobbles/tsv.py index f105fc7..88ffbaa 100644 --- a/vrobbler/apps/scrobbles/tsv.py +++ b/vrobbler/apps/scrobbles/tsv.py @@ -20,7 +20,7 @@ def process_audioscrobbler_tsv_file(file_path, user_id, user_tz=None): user_tz = pytz.utc with open(file_path) as infile: - source = 'Audioscrobbler File' + source = "Audioscrobbler File" rows = csv.reader(infile, delimiter="\t") source_id = "" @@ -32,8 +32,8 @@ def process_audioscrobbler_tsv_file(file_path, user_id, user_tz=None): continue if len(row) > 8: logger.warning( - 'Improper row length during Audioscrobbler import', - extra={'row': row}, + "Improper row length during Audioscrobbler import", + extra={"row": row}, ) continue artist = get_or_create_artist(row[0]) @@ -75,6 +75,6 @@ def process_audioscrobbler_tsv_file(file_path, user_id, user_tz=None): created = Scrobble.objects.bulk_create(new_scrobbles) logger.info( f"Created {len(created)} scrobbles", - extra={'created_scrobbles': created}, + extra={"created_scrobbles": created}, ) return created diff --git a/vrobbler/apps/scrobbles/urls.py b/vrobbler/apps/scrobbles/urls.py index e19a685..2b0eeb9 100644 --- a/vrobbler/apps/scrobbles/urls.py +++ b/vrobbler/apps/scrobbles/urls.py @@ -1,70 +1,70 @@ from django.urls import path from scrobbles import views -app_name = 'scrobbles' +app_name = "scrobbles" urlpatterns = [ path( - 'manual/imdb/', + "manual/imdb/", views.ManualScrobbleView.as_view(), - name='imdb-manual-scrobble', + name="imdb-manual-scrobble", ), path( - 'manual/audioscrobbler/', + "manual/audioscrobbler/", views.AudioScrobblerImportCreateView.as_view(), - name='audioscrobbler-file-upload', + name="audioscrobbler-file-upload", ), path( - 'manual/koreader/', + "manual/koreader/", views.KoReaderImportCreateView.as_view(), - name='koreader-file-upload', + name="koreader-file-upload", ), - path('finish/', views.scrobble_finish, name='finish'), - path('cancel/', views.scrobble_cancel, name='cancel'), + path("finish/", views.scrobble_finish, name="finish"), + path("cancel/", views.scrobble_cancel, name="cancel"), path( - 'upload/', + "upload/", views.AudioScrobblerImportCreateView.as_view(), - name='audioscrobbler-file-upload', + name="audioscrobbler-file-upload", ), path( - 'lastfm-import/', + "lastfm-import/", views.lastfm_import, - name='lastfm-import', + name="lastfm-import", ), path( - 'webhook/jellyfin/', + "webhook/jellyfin/", views.jellyfin_webhook, - name='jellyfin-webhook', + name="jellyfin-webhook", ), path( - 'webhook/mopidy/', + "webhook/mopidy/", views.mopidy_webhook, - name='mopidy-webhook', + name="mopidy-webhook", ), - path('export/', views.export, name='export'), + path("export/", views.export, name="export"), path( - 'imports/', + "imports/", views.ScrobbleImportListView.as_view(), - name='import-detail', + name="import-detail", ), path( - 'imports/tsv//', + "imports/tsv//", views.ScrobbleTSVImportDetailView.as_view(), - name='tsv-import-detail', + name="tsv-import-detail", ), path( - 'imports/lastfm//', + "imports/lastfm//", views.ScrobbleLastFMImportDetailView.as_view(), - name='lastfm-import-detail', + name="lastfm-import-detail", ), path( - 'imports/koreader//', + "imports/koreader//", views.ScrobbleKoReaderImportDetailView.as_view(), - name='koreader-import-detail', + name="koreader-import-detail", ), path( - 'charts/', + "charts/", views.ChartRecordView.as_view(), - name='charts-home', + name="charts-home", ), ] diff --git a/vrobbler/apps/scrobbles/utils.py b/vrobbler/apps/scrobbles/utils.py index 7a17e8c..0b029ae 100644 --- a/vrobbler/apps/scrobbles/utils.py +++ b/vrobbler/apps/scrobbles/utils.py @@ -29,7 +29,7 @@ def convert_to_seconds(run_time: str) -> int: def parse_mopidy_uri(uri: str) -> dict: logger.debug(f"Parsing URI: {uri}") - parsed_uri = uri.split('/') + parsed_uri = uri.split("/") episode_str = unquote(parsed_uri.pop(-1).strip(".mp3")) podcast_str = unquote(parsed_uri.pop(-1)) @@ -43,9 +43,9 @@ def parse_mopidy_uri(uri: str) -> dict: try: if pub_date: - episode_num = int(episode_str.split('-')[3]) + episode_num = int(episode_str.split("-")[3]) else: - episode_num = int(episode_str.split('-')[0]) + episode_num = int(episode_str.split("-")[0]) except IndexError: episode_num = None except ValueError: @@ -59,14 +59,14 @@ def parse_mopidy_uri(uri: str) -> dict: episode_num_gap = len(str(episode_num)) + 1 episode_str = episode_str.strip(episode_str[:episode_num_gap]) - episode_str = episode_str.replace('-', ' ') + episode_str = episode_str.replace("-", " ") logger.debug(f"Found episode name {episode_str} from Mopidy URI") return { - 'episode_filename': episode_str, - 'episode_num': episode_num, - 'podcast_name': podcast_str, - 'pub_date': pub_date, + "episode_filename": episode_str, + "episode_num": episode_num, + "podcast_name": podcast_str, + "pub_date": pub_date, } @@ -99,19 +99,19 @@ def check_scrobble_for_finish( "in_progress", "is_paused", "played_to_completion", - 'playback_position_ticks', + "playback_position_ticks", ] ) if scrobble.percent_played % 5 == 0: if getattr(settings, "KEEP_DETAILED_SCROBBLE_LOGS", False): scrobble.scrobble_log += f"\n{str(scrobble.timestamp)} - {scrobble.playback_position} - {str(scrobble.playback_position_ticks)} - {str(scrobble.percent_played)}%" - scrobble.save(update_fields=['scrobble_log']) + scrobble.save(update_fields=["scrobble_log"]) def get_scrobbles_for_media(media_obj, user: User) -> models.QuerySet: from scrobbles.models import Scrobble - if media_obj.__class__.__name__ == 'Book': + if media_obj.__class__.__name__ == "Book": media_query = models.Q(book=media_obj) return Scrobble.objects.filter(media_query, user=user) diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index e25b0ca..8123278 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -70,28 +70,28 @@ class RecentScrobbleList(ListView): completed_for_user = Scrobble.objects.filter( played_to_completion=True, user=user ) - data['video_scrobble_list'] = completed_for_user.filter( + data["video_scrobble_list"] = completed_for_user.filter( video__isnull=False - ).order_by('-timestamp')[:15] + ).order_by("-timestamp")[:15] - data['podcast_scrobble_list'] = completed_for_user.filter( + data["podcast_scrobble_list"] = completed_for_user.filter( podcast_episode__isnull=False - ).order_by('-timestamp')[:15] + ).order_by("-timestamp")[:15] - data['sport_scrobble_list'] = completed_for_user.filter( + data["sport_scrobble_list"] = completed_for_user.filter( sport_event__isnull=False - ).order_by('-timestamp')[:15] + ).order_by("-timestamp")[:15] - data['active_imports'] = AudioScrobblerTSVImport.objects.filter( + data["active_imports"] = AudioScrobblerTSVImport.objects.filter( processing_started__isnull=False, processed_finished__isnull=True, user=self.request.user, ) limit = 14 - artist = {'user': user, 'media_type': 'Artist', 'limit': limit} + artist = {"user": user, "media_type": "Artist", "limit": limit} # This is weird. They don't display properly as QuerySets, so we cast to lists - data['current_artist_charts'] = { + data["current_artist_charts"] = { "today": list(live_charts(**artist, chart_period="today")), "week": list(live_charts(**artist, chart_period="week")), "month": list(live_charts(**artist, chart_period="month")), @@ -99,8 +99,8 @@ class RecentScrobbleList(ListView): "all": list(live_charts(**artist)), } - track = {'user': user, 'media_type': 'Track', 'limit': limit} - data['current_track_charts'] = { + track = {"user": user, "media_type": "Track", "limit": limit} + data["current_track_charts"] = { "today": list(live_charts(**track, chart_period="today")), "week": list(live_charts(**track, chart_period="week")), "month": list(live_charts(**track, chart_period="month")), @@ -109,15 +109,15 @@ class RecentScrobbleList(ListView): } data["weekly_data"] = week_of_scrobbles(user=user) - data['counts'] = scrobble_counts(user) - data['imdb_form'] = ScrobbleForm - data['export_form'] = ExportScrobbleForm + data["counts"] = scrobble_counts(user) + data["imdb_form"] = ScrobbleForm + data["export_form"] = ExportScrobbleForm return data def get_queryset(self): return Scrobble.objects.filter( track__isnull=False, in_progress=False - ).order_by('-timestamp')[:15] + ).order_by("-timestamp")[:15] class ScrobbleImportListView(TemplateView): @@ -125,22 +125,22 @@ class ScrobbleImportListView(TemplateView): def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) - context_data['object_list'] = [] + context_data["object_list"] = [] context_data["tsv_imports"] = AudioScrobblerTSVImport.objects.filter( user=self.request.user, - ).order_by('-processing_started') + ).order_by("-processing_started") context_data["koreader_imports"] = KoReaderImport.objects.filter( user=self.request.user, - ).order_by('-processing_started') + ).order_by("-processing_started") context_data["lastfm_imports"] = LastFmImport.objects.filter( user=self.request.user, - ).order_by('-processing_started') + ).order_by("-processing_started") return context_data class BaseScrobbleImportDetailView(DetailView): - slug_field = 'uuid' + slug_field = "uuid" template_name = "scrobbles/import_detail.html" def get_queryset(self): @@ -155,7 +155,7 @@ class BaseScrobbleImportDetailView(DetailView): title = "Audioscrobbler TSV Import" if self.model == LastFmImport: title = "LastFM Import" - context_data['title'] = title + context_data["title"] = title return context_data @@ -173,15 +173,15 @@ class ScrobbleLastFMImportDetailView(BaseScrobbleImportDetailView): class ManualScrobbleView(FormView): form_class = ScrobbleForm - template_name = 'scrobbles/manual_form.html' + template_name = "scrobbles/manual_form.html" def form_valid(self, form): - item_id = form.cleaned_data.get('item_id') + item_id = form.cleaned_data.get("item_id") data_dict = None - if 'tt' in item_id: + if "tt" in item_id: data_dict = lookup_video_from_imdb( - form.cleaned_data.get('item_id') + form.cleaned_data.get("item_id") ) if data_dict: manual_scrobble_video(data_dict, self.request.user.id) @@ -189,7 +189,7 @@ class ManualScrobbleView(FormView): if not data_dict: logger.debug(f"Looking for sport event with ID {item_id}") data_dict = lookup_event_from_thesportsdb( - form.cleaned_data.get('item_id') + form.cleaned_data.get("item_id") ) if data_dict: manual_scrobble_event(data_dict, self.request.user.id) @@ -205,7 +205,7 @@ class JsonableResponseMixin: def form_invalid(self, form): response = super().form_invalid(form) - if self.request.accepts('text/html'): + if self.request.accepts("text/html"): return response else: return JsonResponse(form.errors, status=400) @@ -215,11 +215,11 @@ class JsonableResponseMixin: # it might do some processing (in the case of CreateView, it will # call form.save() for example). response = super().form_valid(form) - if self.request.accepts('text/html'): + if self.request.accepts("text/html"): return response else: data = { - 'pk': self.object.pk, + "pk": self.object.pk, } return JsonResponse(data) @@ -228,9 +228,9 @@ class AudioScrobblerImportCreateView( LoginRequiredMixin, JsonableResponseMixin, CreateView ): model = AudioScrobblerTSVImport - fields = ['tsv_file'] - template_name = 'scrobbles/upload_form.html' - success_url = reverse_lazy('vrobbler-home') + fields = ["tsv_file"] + template_name = "scrobbles/upload_form.html" + success_url = reverse_lazy("vrobbler-home") def form_valid(self, form): self.object = form.save(commit=False) @@ -244,9 +244,9 @@ class KoReaderImportCreateView( LoginRequiredMixin, JsonableResponseMixin, CreateView ): model = KoReaderImport - fields = ['sqlite_file'] - template_name = 'scrobbles/upload_form.html' - success_url = reverse_lazy('vrobbler-home') + fields = ["sqlite_file"] + template_name = "scrobbles/upload_form.html" + success_url = reverse_lazy("vrobbler-home") def form_valid(self, form): self.object = form.save(commit=False) @@ -257,7 +257,7 @@ class KoReaderImportCreateView( @permission_classes([IsAuthenticated]) -@api_view(['GET']) +@api_view(["GET"]) def lastfm_import(request): lfm_import, created = LastFmImport.objects.get_or_create( user=request.user, processed_finished__isnull=True @@ -265,19 +265,19 @@ def lastfm_import(request): process_lastfm_import.delay(lfm_import.id) - success_url = reverse_lazy('vrobbler-home') + success_url = reverse_lazy("vrobbler-home") return HttpResponseRedirect(success_url) @csrf_exempt @permission_classes([IsAuthenticated]) -@api_view(['POST']) +@api_view(["POST"]) def jellyfin_webhook(request): data_dict = request.data if ( - data_dict['NotificationType'] == 'PlaybackProgress' - and data_dict['ItemType'] == 'Audio' + data_dict["NotificationType"] == "PlaybackProgress" + and data_dict["ItemType"] == "Audio" ): return Response({}, status=status.HTTP_304_NOT_MODIFIED) @@ -298,17 +298,17 @@ def jellyfin_webhook(request): if not scrobble: return Response({}, status=status.HTTP_400_BAD_REQUEST) - return Response({'scrobble_id': scrobble.id}, status=status.HTTP_200_OK) + return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) @csrf_exempt @permission_classes([IsAuthenticated]) -@api_view(['POST']) +@api_view(["POST"]) def mopidy_webhook(request): try: data_dict = json.loads(request.data) except TypeError: - logger.warning('Received Mopidy data as dict, rather than a string') + logger.warning("Received Mopidy data as dict, rather than a string") data_dict = request.data # For making things easier to build new input processors @@ -316,7 +316,7 @@ def mopidy_webhook(request): json_data = json.dumps(data_dict, indent=4) logger.debug(f"{json_data}") - if 'podcast' in data_dict.get('mopidy_uri'): + if "podcast" in data_dict.get("mopidy_uri"): scrobble = mopidy_scrobble_podcast(data_dict, request.user.id) else: scrobble = mopidy_scrobble_track(data_dict, request.user.id) @@ -324,12 +324,12 @@ def mopidy_webhook(request): if not scrobble: return Response({}, status=status.HTTP_400_BAD_REQUEST) - return Response({'scrobble_id': scrobble.id}, status=status.HTTP_200_OK) + return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) @csrf_exempt @permission_classes([IsAuthenticated]) -@api_view(['POST']) +@api_view(["POST"]) @parser_classes([MultiPartParser]) def import_audioscrobbler_file(request): """Takes a TSV file in the Audioscrobbler format, saves it and processes the @@ -344,7 +344,7 @@ def import_audioscrobbler_file(request): if file_serializer.is_valid(): import_file = file_serializer.save() return Response( - {'scrobble_ids': scrobbles_created}, status=status.HTTP_200_OK + {"scrobble_ids": scrobbles_created}, status=status.HTTP_200_OK ) else: return Response( @@ -353,10 +353,10 @@ def import_audioscrobbler_file(request): @permission_classes([IsAuthenticated]) -@api_view(['GET']) +@api_view(["GET"]) def scrobble_finish(request, uuid): user = request.user - success_url = reverse_lazy('vrobbler-home') + success_url = reverse_lazy("vrobbler-home") if not user.is_authenticated: return HttpResponseRedirect(success_url) @@ -375,10 +375,10 @@ def scrobble_finish(request, uuid): @permission_classes([IsAuthenticated]) -@api_view(['GET']) +@api_view(["GET"]) def scrobble_cancel(request, uuid): user = request.user - success_url = reverse_lazy('vrobbler-home') + success_url = reverse_lazy("vrobbler-home") if not user.is_authenticated: return HttpResponseRedirect(success_url) @@ -397,11 +397,11 @@ def scrobble_cancel(request, uuid): @permission_classes([IsAuthenticated]) -@api_view(['GET']) +@api_view(["GET"]) def export(request): - format = request.GET.get('export_type', 'csv') - start = request.GET.get('start') - end = request.GET.get('end') + format = request.GET.get("export_type", "csv") + start = request.GET.get("start") + end = request.GET.get("end") logger.debug(f"Exporting all scrobbles in format {format}") temp_file, extension = export_scrobbles( @@ -410,14 +410,14 @@ def export(request): now = datetime.now() filename = f"vrobbler-export-{str(now)}.{extension}" - response = FileResponse(open(temp_file, 'rb')) + response = FileResponse(open(temp_file, "rb")) response["Content-Disposition"] = f'attachment; filename="{filename}"' return response class ChartRecordView(TemplateView): - template_name = 'scrobbles/chart_index.html' + template_name = "scrobbles/chart_index.html" @staticmethod def get_media_filter(media_type: str = "") -> Q: @@ -450,19 +450,19 @@ class ChartRecordView(TemplateView): ) -> QuerySet: now = timezone.now() params = {} - params['media_type'] = media + params["media_type"] = media if period == "today": - params['day'] = now.day - params['month'] = now.month - params['year'] = now.year + params["day"] = now.day + params["month"] = now.month + params["year"] = now.year if period == "week": - params['week'] = now.ioscalendar()[1] - params['year'] = now.year + params["week"] = now.ioscalendar()[1] + params["year"] = now.year if period == "month": - params['month'] = now.month - params['year'] = now.year + params["month"] = now.month + params["year"] = now.year if period == "year": - params['year'] = now.year + params["year"] = now.year return self.get_chart_records(**params)[:limit] def get_context_data(self, **kwargs): @@ -475,8 +475,8 @@ class ChartRecordView(TemplateView): if not date: limit = 20 - artist_params = {'user': user, 'media_type': 'Artist'} - context_data['current_artist_charts'] = { + artist_params = {"user": user, "media_type": "Artist"} + context_data["current_artist_charts"] = { "today": live_charts( **artist_params, chart_period="today", limit=limit ), @@ -492,8 +492,8 @@ class ChartRecordView(TemplateView): "all": live_charts(**artist_params, limit=limit), } - track_params = {'user': user, 'media_type': 'Track'} - context_data['current_track_charts'] = { + track_params = {"user": user, "media_type": "Track"} + context_data["current_track_charts"] = { "today": live_charts( **track_params, chart_period="today", limit=limit ), @@ -513,34 +513,34 @@ class ChartRecordView(TemplateView): # Date provided, lookup past charts, returning nothing if it's now or in the future. now = timezone.now() year = now.year - params = {'year': year} + params = {"year": year} name = f"Chart for {year}" - date_params = date.split('-') + date_params = date.split("-") year = int(date_params[0]) in_progress = False if len(date_params) == 2: - if 'W' in date_params[1]: + if "W" in date_params[1]: week = int(date_params[1].strip('W"')) - params['week'] = week + params["week"] = week start = datetime.strptime(date + "-1", "%Y-W%W-%w").replace( tzinfo=pytz.utc ) end = start + timedelta(days=6) in_progress = start <= now <= end - as_str = start.strftime('Week of %B %d, %Y') + as_str = start.strftime("Week of %B %d, %Y") name = f"Chart for {as_str}" else: month = int(date_params[1]) - params['month'] = month + params["month"] = month month_str = calendar.month_name[month] name = f"Chart for {month_str} {year}" in_progress = now.month == month and now.year == year if len(date_params) == 3: month = int(date_params[1]) day = int(date_params[2]) - params['month'] = month - params['day'] = day + params["month"] = month + params["day"] = day month_str = calendar.month_name[month] name = f"Chart for {month_str} {day}, {year}" in_progress = ( @@ -573,9 +573,9 @@ class ChartRecordView(TemplateView): media_filter, user=self.request.user, **params ).order_by("rank") - context_data['media_type'] = media_type - context_data['track_charts'] = track_charts - context_data['artist_charts'] = artist_charts - context_data['name'] = " ".join(["Top", media_type, "for", name]) - context_data['in_progress'] = in_progress + context_data["media_type"] = media_type + context_data["track_charts"] = track_charts + context_data["artist_charts"] = artist_charts + context_data["name"] = " ".join(["Top", media_type, "for", name]) + context_data["in_progress"] = in_progress return context_data diff --git a/vrobbler/apps/sports/admin.py b/vrobbler/apps/sports/admin.py index 9d3f0ae..7c9e086 100644 --- a/vrobbler/apps/sports/admin.py +++ b/vrobbler/apps/sports/admin.py @@ -72,6 +72,6 @@ class SportEventAdmin(admin.ModelAdmin): def comp_str(self, obj): if obj.home_team: - return f'{obj.away_team} @ {obj.home_team}' + return f"{obj.away_team} @ {obj.home_team}" if obj.player_one: - return f'{obj.player_one} v {obj.player_two}' + return f"{obj.player_one} v {obj.player_two}" diff --git a/vrobbler/apps/sports/api/views.py b/vrobbler/apps/sports/api/views.py index 30e1d47..6629f1e 100644 --- a/vrobbler/apps/sports/api/views.py +++ b/vrobbler/apps/sports/api/views.py @@ -20,42 +20,42 @@ from sports.models import ( class SportEventViewSet(viewsets.ModelViewSet): - queryset = SportEvent.objects.all().order_by('-created') + queryset = SportEvent.objects.all().order_by("-created") serializer_class = SportEventSerializer permission_classes = [permissions.IsAuthenticated] class LeagueViewSet(viewsets.ModelViewSet): - queryset = League.objects.all().order_by('-created') + queryset = League.objects.all().order_by("-created") serializer_class = LeagueSerializer permission_classes = [permissions.IsAuthenticated] class RoundViewSet(viewsets.ModelViewSet): - queryset = Round.objects.all().order_by('-created') + queryset = Round.objects.all().order_by("-created") serializer_class = RoundSerializer permission_classes = [permissions.IsAuthenticated] class SportViewSet(viewsets.ModelViewSet): - queryset = Sport.objects.all().order_by('-created') + queryset = Sport.objects.all().order_by("-created") serializer_class = SportSerializer permission_classes = [permissions.IsAuthenticated] class PlayerViewSet(viewsets.ModelViewSet): - queryset = Player.objects.all().order_by('-created') + queryset = Player.objects.all().order_by("-created") serializer_class = PlayerSerializer permission_classes = [permissions.IsAuthenticated] class TeamViewSet(viewsets.ModelViewSet): - queryset = Team.objects.all().order_by('-created') + queryset = Team.objects.all().order_by("-created") serializer_class = TeamSerializer permission_classes = [permissions.IsAuthenticated] class SeasonViewSet(viewsets.ModelViewSet): - queryset = Season.objects.all().order_by('-created') + queryset = Season.objects.all().order_by("-created") serializer_class = SeasonSerializer permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/apps/sports/models.py b/vrobbler/apps/sports/models.py index cde2c92..3118c09 100644 --- a/vrobbler/apps/sports/models.py +++ b/vrobbler/apps/sports/models.py @@ -15,10 +15,10 @@ BNULL = {"blank": True, "null": True} class SportEventType(models.TextChoices): - UNKNOWN = 'UK', _('Event') - GAME = 'GA', _('Game') - RACE = 'RA', _('Race') - MATCH = 'MA', _('Match') + UNKNOWN = "UK", _("Event") + GAME = "GA", _("Game") + RACE = "RA", _("Race") + MATCH = "MA", _("Match") class TheSportsDbMixin(TimeStampedModel): @@ -47,7 +47,7 @@ class Sport(TheSportsDbMixin): @property def default_event_run_time_ticks(self): default_run_time = getattr( - settings, 'DEFAULT_EVENT_RUNTIME_SECONDS', 14400 + settings, "DEFAULT_EVENT_RUNTIME_SECONDS", 14400 ) if self.default_event_run_time: default_run_time = self.default_event_run_time @@ -68,7 +68,7 @@ class Season(TheSportsDbMixin): league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL) def __str__(self): - return f'{self.name} season of {self.league}' + return f"{self.name} season of {self.league}" class Team(TheSportsDbMixin): @@ -84,11 +84,11 @@ class Round(TheSportsDbMixin): season = models.ForeignKey(Season, on_delete=models.DO_NOTHING, **BNULL) def __str__(self): - return f'{self.name} of {self.season}' + return f"{self.name} of {self.season}" class SportEvent(ScrobblableMixin): - COMPLETION_PERCENT = getattr(settings, 'SPORT_COMPLETION_PERCENT', 90) + COMPLETION_PERCENT = getattr(settings, "SPORT_COMPLETION_PERCENT", 90) thesportsdb_id = models.CharField(max_length=255, **BNULL) event_type = models.CharField( @@ -101,25 +101,25 @@ class SportEvent(ScrobblableMixin): home_team = models.ForeignKey( Team, on_delete=models.DO_NOTHING, - related_name='home_event_set', + related_name="home_event_set", **BNULL, ) away_team = models.ForeignKey( Team, on_delete=models.DO_NOTHING, - related_name='away_event_set', + related_name="away_event_set", **BNULL, ) player_one = models.ForeignKey( Player, on_delete=models.DO_NOTHING, - related_name='player_one_set', + related_name="player_one_set", **BNULL, ) player_two = models.ForeignKey( Player, on_delete=models.DO_NOTHING, - related_name='player_two_set', + related_name="player_two_set", **BNULL, ) @@ -127,7 +127,7 @@ class SportEvent(ScrobblableMixin): return f"{self.start.date()} - {self.round} - {self.home_team} v {self.away_team}" def get_absolute_url(self): - return reverse("sports:event_detail", kwargs={'slug': self.uuid}) + return reverse("sports:event_detail", kwargs={"slug": self.uuid}) @property def subtitle(self): @@ -153,7 +153,7 @@ class SportEvent(ScrobblableMixin): sport, s_created = Sport.objects.get_or_create(thesportsdb_id=sid) if s_created: sport.name = sid - sport.save(update_fields=['name']) + sport.save(update_fields=["name"]) # Find or create our League lid = data_dict.get("LeagueId") @@ -163,34 +163,34 @@ class SportEvent(ScrobblableMixin): if l_created: league.sport = sport league.name = data_dict.get("LeagueName", "") - league.save(update_fields=['sport', 'name']) + league.save(update_fields=["sport", "name"]) # Find or create our Season - seid = data_dict.get('Season') + seid = data_dict.get("Season") season, se_created = Season.objects.get_or_create( thesportsdb_id=seid, league=league ) if se_created: season.name = seid - season.save(update_fields=['name']) + season.save(update_fields=["name"]) # Find or create our Round - rid = data_dict.get('RoundId') + rid = data_dict.get("RoundId") round, r_created = Round.objects.get_or_create( thesportsdb_id=rid, season=season ) if r_created: round.season = season - round.save(update_fields=['season']) + round.save(update_fields=["season"]) # Set some special data for Tennis player_one = None player_two = None - if data_dict.get('Sport') == 'Tennis': - event_name = data_dict.get('Name', '') + if data_dict.get("Sport") == "Tennis": + event_name = data_dict.get("Name", "") if not round.name: round.name = get_round_name_from_event(event_name) - round.save(update_fields=['name']) + round.save(update_fields=["name"]) players_list = get_players_from_event(event_name) player_one = Player.objects.filter( @@ -229,7 +229,7 @@ class SportEvent(ScrobblableMixin): "away_team": away_team, "player_one": player_one, "player_two": player_two, - "start": data_dict['Start'], + "start": data_dict["Start"], "round": round, "run_time_ticks": data_dict.get("RunTimeTicks"), "run_time": data_dict.get("RunTime", ""), diff --git a/vrobbler/apps/sports/thesportsdb.py b/vrobbler/apps/sports/thesportsdb.py index 4107a33..65b9bb8 100644 --- a/vrobbler/apps/sports/thesportsdb.py +++ b/vrobbler/apps/sports/thesportsdb.py @@ -14,36 +14,36 @@ client = TheSportsDbClient(api_key=API_KEY) def lookup_event_from_thesportsdb(event_id: str) -> dict: - event = client.lookup_event(event_id)['events'][0] + event = client.lookup_event(event_id)["events"][0] if not event or type(event) != dict: return {} league = {} # client.lookup_league(league_id=event.get('idLeague')) event_type = "Game" sport, _created = Sport.objects.get_or_create( - thesportsdb_id=event.get('strSport') + thesportsdb_id=event.get("strSport") ) data_dict = { "EventId": event_id, "ItemType": sport.default_event_type, - "Name": event.get('strEvent'), - "AltName": event.get('strEventAlternate'), - "Start": parse(event.get('strTimestamp')), - "Provider_thesportsdb": event.get('idEvent'), + "Name": event.get("strEvent"), + "AltName": event.get("strEventAlternate"), + "Start": parse(event.get("strTimestamp")), + "Provider_thesportsdb": event.get("idEvent"), "RunTime": sport.default_event_run_time, "RunTimeTicks": sport.default_event_run_time_ticks, - "Sport": event.get('strSport'), - "Season": event.get('strSeason'), - "LeagueId": event.get('idLeague'), - "LeagueName": event.get('strLeague'), - "HomeTeamId": event.get('idHomeTeam'), - "HomeTeamName": event.get('strHomeTeam'), - "AwayTeamId": event.get('idAwayTeam'), - "AwayTeamName": event.get('strAwayTeam'), - "RoundId": event.get('intRound'), + "Sport": event.get("strSport"), + "Season": event.get("strSeason"), + "LeagueId": event.get("idLeague"), + "LeagueName": event.get("strLeague"), + "HomeTeamId": event.get("idHomeTeam"), + "HomeTeamName": event.get("strHomeTeam"), + "AwayTeamId": event.get("idAwayTeam"), + "AwayTeamName": event.get("strAwayTeam"), + "RoundId": event.get("intRound"), "PlaybackPositionTicks": None, "PlaybackPosition": None, - "UtcTimestamp": timezone.now().strftime('%Y-%m-%d %H:%M:%S.%f%z'), + "UtcTimestamp": timezone.now().strftime("%Y-%m-%d %H:%M:%S.%f%z"), "IsPaused": False, "PlayedToCompletion": False, "Source": "Vrobbler", diff --git a/vrobbler/apps/sports/urls.py b/vrobbler/apps/sports/urls.py index 740a5c8..10693f8 100644 --- a/vrobbler/apps/sports/urls.py +++ b/vrobbler/apps/sports/urls.py @@ -1,18 +1,18 @@ from django.urls import path from sports import views -app_name = 'sports' +app_name = "sports" urlpatterns = [ path( - 'sport-events/', + "sport-events/", views.SportEventListView.as_view(), - name='event_list', + name="event_list", ), path( - 'sport-events//', + "sport-events//", views.SportEventDetailView.as_view(), - name='event_detail', + name="event_detail", ), ] diff --git a/vrobbler/apps/sports/utils.py b/vrobbler/apps/sports/utils.py index 5df09c5..f0dbe1a 100644 --- a/vrobbler/apps/sports/utils.py +++ b/vrobbler/apps/sports/utils.py @@ -1,11 +1,11 @@ def get_round_name_from_event(event: str) -> str: - return ' '.join(event.split(' ')[:2]) + return " ".join(event.split(" ")[:2]) def get_players_from_event(event: str) -> list[str]: players = [] event_name = get_round_name_from_event(event) - players_list = event.split(event_name)[1:][0].split('vs') + players_list = event.split(event_name)[1:][0].split("vs") players.append(players_list[0].strip()) players.append(players_list[1].strip()) return players diff --git a/vrobbler/apps/sports/views.py b/vrobbler/apps/sports/views.py index 454416f..5b156ce 100644 --- a/vrobbler/apps/sports/views.py +++ b/vrobbler/apps/sports/views.py @@ -9,4 +9,4 @@ class SportEventListView(generic.ListView): class SportEventDetailView(generic.DetailView): model = SportEvent - slug_field = 'uuid' + slug_field = "uuid" diff --git a/vrobbler/apps/videos/admin.py b/vrobbler/apps/videos/admin.py index 8bd8c98..b19e355 100644 --- a/vrobbler/apps/videos/admin.py +++ b/vrobbler/apps/videos/admin.py @@ -14,7 +14,7 @@ class SeriesAdmin(admin.ModelAdmin): @admin.register(Video) class VideoAdmin(admin.ModelAdmin): date_hierarchy = "created" - raw_id_fields = ('tv_series',) + raw_id_fields = ("tv_series",) list_display = ( "title", "video_type", diff --git a/vrobbler/apps/videos/api/views.py b/vrobbler/apps/videos/api/views.py index f9b2503..2a2aeb5 100644 --- a/vrobbler/apps/videos/api/views.py +++ b/vrobbler/apps/videos/api/views.py @@ -8,12 +8,12 @@ from videos.models import Series, Video class SeriesViewSet(viewsets.ModelViewSet): - queryset = Series.objects.all().order_by('-created') + queryset = Series.objects.all().order_by("-created") serializer_class = SeriesSerializer permission_classes = [permissions.IsAuthenticated] class VideoViewSet(viewsets.ModelViewSet): - queryset = Video.objects.all().order_by('-created') + queryset = Video.objects.all().order_by("-created") serializer_class = VideoSerializer permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/apps/videos/apps.py b/vrobbler/apps/videos/apps.py index b374b65..722b34c 100644 --- a/vrobbler/apps/videos/apps.py +++ b/vrobbler/apps/videos/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class VideosConfig(AppConfig): - name = 'videos' + name = "videos" diff --git a/vrobbler/apps/videos/imdb.py b/vrobbler/apps/videos/imdb.py index b00a4c7..30f6854 100644 --- a/vrobbler/apps/videos/imdb.py +++ b/vrobbler/apps/videos/imdb.py @@ -10,11 +10,11 @@ logger = logging.getLogger(__name__) def lookup_video_from_imdb(imdb_id: str) -> dict: - if 'tt' not in imdb_id: + if "tt" not in imdb_id: logger.warning(f"IMDB ID should begin with 'tt' {imdb_id}") return - lookup_id = imdb_id.strip('tt') + lookup_id = imdb_id.strip("tt") media = imdb_client.get_movie(lookup_id) run_time_seconds = 60 * 60 @@ -26,11 +26,11 @@ def lookup_video_from_imdb(imdb_id: str) -> dict: run_time_ticks = run_time_seconds * 1000 * 1000 item_type = "Movie" - if media.get('series title'): + if media.get("series title"): item_type = "Episode" try: - plot = media.get('plot')[0] + plot = media.get("plot")[0] except TypeError: plot = "" except IndexError: @@ -40,19 +40,19 @@ def lookup_video_from_imdb(imdb_id: str) -> dict: # Build a rough approximation of a Jellyfin data response data_dict = { "ItemType": item_type, - "Name": media.get('title'), + "Name": media.get("title"), "Overview": plot, - "Tagline": media.get('tagline'), - "Year": media.get('year'), + "Tagline": media.get("tagline"), + "Year": media.get("year"), "Provider_imdb": imdb_id, "RunTime": run_time_seconds, "RunTimeTicks": run_time_ticks, - "SeriesName": media.get('series title'), - "EpisodeNumber": media.get('episode'), - "SeasonNumber": media.get('season'), + "SeriesName": media.get("series title"), + "EpisodeNumber": media.get("episode"), + "SeasonNumber": media.get("season"), "PlaybackPositionTicks": 1, "PlaybackPosition": 1, - "UtcTimestamp": timezone.now().strftime('%Y-%m-%d %H:%M:%S.%f%z'), + "UtcTimestamp": timezone.now().strftime("%Y-%m-%d %H:%M:%S.%f%z"), "IsPaused": False, "PlayedToCompletion": False, } diff --git a/vrobbler/apps/videos/models.py b/vrobbler/apps/videos/models.py index 7c8b762..cb0747c 100644 --- a/vrobbler/apps/videos/models.py +++ b/vrobbler/apps/videos/models.py @@ -33,13 +33,13 @@ class Series(TimeStampedModel): class Video(ScrobblableMixin): - COMPLETION_PERCENT = getattr(settings, 'VIDEO_COMPLETION_PERCENT', 90) - SECONDS_TO_STALE = getattr(settings, 'VIDEO_SECONDS_TO_STALE', 14400) + COMPLETION_PERCENT = getattr(settings, "VIDEO_COMPLETION_PERCENT", 90) + SECONDS_TO_STALE = getattr(settings, "VIDEO_SECONDS_TO_STALE", 14400) class VideoType(models.TextChoices): - UNKNOWN = 'U', _('Unknown') - TV_EPISODE = 'E', _('TV Episode') - MOVIE = 'M', _('Movie') + UNKNOWN = "U", _("Unknown") + TV_EPISODE = "E", _("TV Episode") + MOVIE = "M", _("Movie") video_type = models.CharField( max_length=1, @@ -59,7 +59,7 @@ class Video(ScrobblableMixin): tvrage_id = models.CharField(max_length=20, **BNULL) class Meta: - unique_together = [['title', 'imdb_id']] + unique_together = [["title", "imdb_id"]] def __str__(self): if self.video_type == self.VideoType.TV_EPISODE: @@ -67,7 +67,7 @@ class Video(ScrobblableMixin): return self.title def get_absolute_url(self): - return reverse("videos:video_detail", kwargs={'slug': self.uuid}) + return reverse("videos:video_detail", kwargs={"slug": self.uuid}) @property def subtitle(self): @@ -106,7 +106,7 @@ class Video(ScrobblableMixin): series, series_created = Series.objects.get_or_create( name=series_name ) - video_dict['video_type'] = Video.VideoType.TV_EPISODE + video_dict["video_type"] = Video.VideoType.TV_EPISODE video, created = cls.objects.get_or_create(**video_dict) diff --git a/vrobbler/apps/videos/urls.py b/vrobbler/apps/videos/urls.py index 3686caa..fe2a1e9 100644 --- a/vrobbler/apps/videos/urls.py +++ b/vrobbler/apps/videos/urls.py @@ -1,21 +1,21 @@ from django.urls import path from videos import views -app_name = 'videos' +app_name = "videos" urlpatterns = [ # path('', views.scrobble_endpoint, name='scrobble-list'), - path("movies/", views.MovieListView.as_view(), name='movie_list'), - path('series/', views.SeriesListView.as_view(), name='series_list'), + path("movies/", views.MovieListView.as_view(), name="movie_list"), + path("series/", views.SeriesListView.as_view(), name="series_list"), path( - 'series//', + "series//", views.SeriesDetailView.as_view(), - name='series_detail', + name="series_detail", ), path( - 'video//', + "video//", views.VideoDetailView.as_view(), - name='video_detail', + name="video_detail", ), ] diff --git a/vrobbler/apps/videos/views.py b/vrobbler/apps/videos/views.py index 5b29d77..4440cae 100644 --- a/vrobbler/apps/videos/views.py +++ b/vrobbler/apps/videos/views.py @@ -18,9 +18,9 @@ class SeriesListView(generic.ListView): class SeriesDetailView(generic.DetailView): model = Series - slug_field = 'uuid' + slug_field = "uuid" class VideoDetailView(generic.DetailView): model = Video - slug_field = 'uuid' + slug_field = "uuid" diff --git a/vrobbler/asgi.py b/vrobbler/asgi.py index 734488d..16a00a5 100644 --- a/vrobbler/asgi.py +++ b/vrobbler/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'vrobbler.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vrobbler.settings") application = get_asgi_application() diff --git a/vrobbler/cli.py b/vrobbler/cli.py index 15af6ea..f1cdf08 100644 --- a/vrobbler/cli.py +++ b/vrobbler/cli.py @@ -4,10 +4,10 @@ import sys from os import environ as env -if not 'DJANGO_SETTINGS_MODULE' in env: +if not "DJANGO_SETTINGS_MODULE" in env: from vrobbler import settings - env.setdefault('DJANGO_SETTINGS_MODULE', settings.__name__) + env.setdefault("DJANGO_SETTINGS_MODULE", settings.__name__) import django @@ -15,7 +15,7 @@ import django django.setup() # this line must be after django.setup() for logging configure -logger = logging.getLogger('vrobbler') +logger = logging.getLogger("vrobbler") def main(): @@ -33,5 +33,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/vrobbler/settings.py b/vrobbler/settings.py index 69e601f..a35d7b0 100644 --- a/vrobbler/settings.py +++ b/vrobbler/settings.py @@ -9,7 +9,7 @@ from dotenv import load_dotenv PROJECT_ROOT = Path(__file__).resolve().parent BASE_DIR = Path(__file__).resolve().parent.parent -sys.path.insert(0, os.path.join(PROJECT_ROOT, 'apps')) +sys.path.insert(0, os.path.join(PROJECT_ROOT, "apps")) # Tap vrobbler.conf if it's available if os.path.exists("vrobbler.conf"): @@ -57,7 +57,7 @@ TMDB_API_KEY = os.getenv("VROBBLER_TMDB_API_KEY", "") LASTFM_API_KEY = os.getenv("VROBBLER_LASTFM_API_KEY") LASTFM_SECRET_KEY = os.getenv("VROBBLER_LASTFM_SECRET_KEY") -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" TIME_ZONE = os.getenv("VROBBLER_TIME_ZONE", "US/Eastern") @@ -159,9 +159,9 @@ if TESTING: } db_str = "" -if 'sqlite' in DATABASES['default']['ENGINE']: +if "sqlite" in DATABASES["default"]["ENGINE"]: db_str = f"Connected to sqlite@{DATABASES['default']['NAME']}" -if 'postgresql' in DATABASES['default']['ENGINE']: +if "postgresql" in DATABASES["default"]["ENGINE"]: db_str = f"Connected to postgres@{DATABASES['default']['HOST']}/{DATABASES['default']['NAME']}" if db_str: print(db_str) @@ -187,11 +187,11 @@ AUTHENTICATION_BACKENDS = [ # We have to ignore content negotiation because Jellyfin is a bad actor REST_FRAMEWORK = { "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.AllowAny",), - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.TokenAuthentication', - 'rest_framework.authentication.SessionAuthentication', + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.TokenAuthentication", + "rest_framework.authentication.SessionAuthentication", ], - 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'vrobbler.negotiation.IgnoreClientContentNegotiation', + "DEFAULT_CONTENT_NEGOTIATION_CLASS": "vrobbler.negotiation.IgnoreClientContentNegotiation", "DEFAULT_FILTER_BACKENDS": [ "django_filters.rest_framework.DjangoFilterBackend" ], @@ -289,17 +289,17 @@ LOGGING = { "class": "logging.NullHandler", "level": LOG_LEVEL, }, - 'sql': { - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': ''.join([LOG_FILE_PATH, 'vrobbler_sql.', LOG_TYPE]), - 'formatter': LOG_TYPE, - 'level': LOG_LEVEL, + "sql": { + "class": "logging.handlers.RotatingFileHandler", + "filename": "".join([LOG_FILE_PATH, "vrobbler_sql.", LOG_TYPE]), + "formatter": LOG_TYPE, + "level": LOG_LEVEL, }, - 'file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': ''.join([LOG_FILE_PATH, 'vrobbler.', LOG_TYPE]), - 'formatter': LOG_TYPE, - 'level': LOG_LEVEL, + "file": { + "class": "logging.handlers.RotatingFileHandler", + "filename": "".join([LOG_FILE_PATH, "vrobbler.", LOG_TYPE]), + "formatter": LOG_TYPE, + "level": LOG_LEVEL, }, }, "loggers": { @@ -322,5 +322,5 @@ LOGGING = { LOG_TO_CONSOLE = os.getenv("VROBBLER_LOG_TO_CONSOLE", False) if LOG_TO_CONSOLE: - LOGGING['loggers']['django']['handlers'] = ["console"] - LOGGING['loggers']['vrobbler']['handlers'] = ["console"] + LOGGING["loggers"]["django"]["handlers"] = ["console"] + LOGGING["loggers"]["vrobbler"]["handlers"] = ["console"] diff --git a/vrobbler/urls.py b/vrobbler/urls.py index 825eee3..bce2792 100644 --- a/vrobbler/urls.py +++ b/vrobbler/urls.py @@ -31,29 +31,29 @@ from vrobbler.apps.videos import urls as video_urls from vrobbler.apps.videos.api.views import SeriesViewSet, VideoViewSet router = routers.DefaultRouter() -router.register(r'scrobbles', ScrobbleViewSet) -router.register(r'lastfm-imports', LastFmImportViewSet) -router.register(r'tsv-imports', AudioScrobblerTSVImportViewSet) -router.register(r'koreader-imports', KoReaderImportViewSet) -router.register(r'artist', ArtistViewSet) -router.register(r'album', AlbumViewSet) -router.register(r'tracks', TrackViewSet) -router.register(r'series', SeriesViewSet) -router.register(r'videos', VideoViewSet) -router.register(r'authors', AuthorViewSet) -router.register(r'books', BookViewSet) -router.register(r'leagues', LeagueViewSet) -router.register(r'sports', SportViewSet) -router.register(r'seasons', SeasonViewSet) -router.register(r'players', PlayerViewSet) -router.register(r'sport-events', SportEventViewSet) -router.register(r'teams', TeamViewSet) -router.register(r'users', UserViewSet) -router.register(r'user_profiles', UserProfileViewSet) +router.register(r"scrobbles", ScrobbleViewSet) +router.register(r"lastfm-imports", LastFmImportViewSet) +router.register(r"tsv-imports", AudioScrobblerTSVImportViewSet) +router.register(r"koreader-imports", KoReaderImportViewSet) +router.register(r"artist", ArtistViewSet) +router.register(r"album", AlbumViewSet) +router.register(r"tracks", TrackViewSet) +router.register(r"series", SeriesViewSet) +router.register(r"videos", VideoViewSet) +router.register(r"authors", AuthorViewSet) +router.register(r"books", BookViewSet) +router.register(r"leagues", LeagueViewSet) +router.register(r"sports", SportViewSet) +router.register(r"seasons", SeasonViewSet) +router.register(r"players", PlayerViewSet) +router.register(r"sport-events", SportEventViewSet) +router.register(r"teams", TeamViewSet) +router.register(r"users", UserViewSet) +router.register(r"user_profiles", UserProfileViewSet) urlpatterns = [ - path('api/v1/', include(router.urls)), - path('api/v1/auth', include("rest_framework.urls")), + path("api/v1/", include(router.urls)), + path("api/v1/auth", include("rest_framework.urls")), path("admin/", admin.site.urls), path("accounts/", include("allauth.urls")), path("", include(music_urls, namespace="music")), diff --git a/vrobbler/wsgi.py b/vrobbler/wsgi.py index 513a653..4732b17 100644 --- a/vrobbler/wsgi.py +++ b/vrobbler/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'vrobbler.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vrobbler.settings") application = get_wsgi_application()