Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 907ef802bc | |||
| d700b581a1 | |||
| 7605c672f6 | |||
| 8d1df806d7 | |||
| 0f562b7c58 | |||
| fe53b68714 | |||
| 7e2915850f | |||
| 90687a6b43 | |||
| 07cfb03eb6 | |||
| 58be8d26a0 | |||
| 0fede269b1 | |||
| 6cdcf4ff6f | |||
| 0634b94368 | |||
| 0ab7c563cf |
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "vrobbler"
|
||||
version = "0.6.0"
|
||||
version = "0.6.2"
|
||||
description = ""
|
||||
authors = ["Colin Powell <colin@unbl.ink>"]
|
||||
|
||||
|
||||
@ -71,8 +71,8 @@ def top_tracks(filter: str = "today", limit: int = 15) -> List["Track"]:
|
||||
time_filter = Q(scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_YEAR)
|
||||
|
||||
return (
|
||||
Track.objects.annotate(num_scrobbles=Count("scrobble", distinct=True))
|
||||
.filter(time_filter)
|
||||
Track.objects.filter(time_filter)
|
||||
.annotate(num_scrobbles=Count("scrobble", distinct=True))
|
||||
.order_by("-num_scrobbles")[:limit]
|
||||
)
|
||||
|
||||
@ -93,10 +93,8 @@ def top_artists(filter: str = "today", limit: int = 15) -> List["Artist"]:
|
||||
)
|
||||
|
||||
return (
|
||||
Artist.objects.annotate(
|
||||
num_scrobbles=Count("track__scrobble", distinct=True)
|
||||
)
|
||||
.filter(time_filter)
|
||||
Artist.objects.filter(time_filter)
|
||||
.annotate(num_scrobbles=Count("track__scrobble", distinct=True))
|
||||
.order_by("-num_scrobbles")[:limit]
|
||||
)
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ from uuid import uuid4
|
||||
|
||||
import musicbrainzngs
|
||||
from django.apps.config import cached_property
|
||||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -93,6 +94,8 @@ class Album(TimeStampedModel):
|
||||
|
||||
|
||||
class Track(ScrobblableMixin):
|
||||
COMPLETION_PERCENT = getattr(settings, 'MUSIC_COMPLETION_PERCENT', 90)
|
||||
|
||||
class Opinion(models.IntegerChoices):
|
||||
DOWN = -1, 'Thumbs down'
|
||||
NEUTRAL = 0, 'No opinion'
|
||||
|
||||
@ -2,6 +2,7 @@ import logging
|
||||
from typing import Dict, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_extensions.db.models import TimeStampedModel
|
||||
@ -34,6 +35,8 @@ class Podcast(TimeStampedModel):
|
||||
|
||||
|
||||
class Episode(ScrobblableMixin):
|
||||
COMPLETION_PERCENT = getattr(settings, 'PODCAST_COMPLETION_PERCENT', 90)
|
||||
|
||||
podcast = models.ForeignKey(Podcast, on_delete=models.DO_NOTHING)
|
||||
number = models.IntegerField(**BNULL)
|
||||
pub_date = models.DateField(**BNULL)
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
#!/usr/bin/env python3
|
||||
JELLYFIN_VIDEO_ITEM_TYPES = ["Episode", "Movie"]
|
||||
JELLYFIN_AUDIO_ITEM_TYPES = ["Audio"]
|
||||
|
||||
@ -19,7 +19,11 @@ def lookup_video_from_imdb(imdb_id: str) -> dict:
|
||||
lookup_id = imdb_id.strip('tt')
|
||||
media = imdb_client.get_movie(lookup_id)
|
||||
|
||||
run_time_seconds = int(media.get('runtimes')[0]) * 60
|
||||
run_time_seconds = 60 * 60
|
||||
runtimes = media.get("runtimes")
|
||||
if runtimes:
|
||||
run_time_seconds = int(runtimes)[0] * 60
|
||||
|
||||
# Ticks otherwise known as miliseconds
|
||||
run_time_ticks = run_time_seconds * 1000 * 1000
|
||||
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
@ -16,10 +14,6 @@ from sports.models import SportEvent
|
||||
logger = logging.getLogger(__name__)
|
||||
User = get_user_model()
|
||||
BNULL = {"blank": True, "null": True}
|
||||
VIDEO_BACKOFF = getattr(settings, 'VIDEO_BACKOFF_MINUTES')
|
||||
TRACK_BACKOFF = getattr(settings, 'MUSIC_BACKOFF_SECONDS')
|
||||
VIDEO_WAIT_PERIOD = getattr(settings, 'VIDEO_WAIT_PERIOD_DAYS')
|
||||
TRACK_WAIT_PERIOD = getattr(settings, 'MUSIC_WAIT_PERIOD_MINUTES')
|
||||
|
||||
|
||||
class Scrobble(TimeStampedModel):
|
||||
@ -46,26 +40,20 @@ class Scrobble(TimeStampedModel):
|
||||
|
||||
@property
|
||||
def percent_played(self) -> int:
|
||||
playback_ticks = None
|
||||
percent_played = 100
|
||||
|
||||
if not self.media_obj.run_time_ticks:
|
||||
logger.warning(
|
||||
f"{self} has no run_time_ticks value, cannot show percent played"
|
||||
)
|
||||
return percent_played
|
||||
return 100
|
||||
|
||||
playback_ticks = self.playback_position_ticks
|
||||
if not playback_ticks:
|
||||
logger.info(
|
||||
"No playback_position_ticks, estimating based on creation time"
|
||||
)
|
||||
playback_ticks = (timezone.now() - self.timestamp).seconds * 1000
|
||||
|
||||
percent = int((playback_ticks / self.media_obj.run_time_ticks) * 100)
|
||||
if self.played_to_completion:
|
||||
playback_ticks = self.media_obj.run_time_ticks
|
||||
|
||||
if percent > 100:
|
||||
percent = 100
|
||||
percent = int((playback_ticks / self.media_obj.run_time_ticks) * 100)
|
||||
return percent
|
||||
|
||||
@property
|
||||
@ -82,34 +70,54 @@ class Scrobble(TimeStampedModel):
|
||||
return media_obj
|
||||
|
||||
def __str__(self):
|
||||
return f"Scrobble of {self.media_obj} {self.timestamp.year}-{self.timestamp.month}"
|
||||
timestamp = self.timestamp.strftime('%Y-%m-%d')
|
||||
return f"Scrobble of {self.media_obj} ({timestamp})"
|
||||
|
||||
@classmethod
|
||||
def create_or_update_for_video(
|
||||
cls, video: "Video", user_id: int, jellyfin_data: dict
|
||||
cls, video: "Video", user_id: int, scrobble_data: dict
|
||||
) -> "Scrobble":
|
||||
jellyfin_data['video_id'] = video.id
|
||||
scrobble_data['video_id'] = video.id
|
||||
|
||||
scrobble = (
|
||||
cls.objects.filter(video=video, user_id=user_id)
|
||||
cls.objects.filter(
|
||||
video=video,
|
||||
user_id=user_id,
|
||||
played_to_completion=False,
|
||||
)
|
||||
.order_by('-modified')
|
||||
.first()
|
||||
)
|
||||
if scrobble:
|
||||
logger.info(
|
||||
f"Found existing scrobble for video {video}, updating",
|
||||
{"scrobble_data": scrobble_data},
|
||||
)
|
||||
return cls.update(scrobble, scrobble_data)
|
||||
|
||||
# Backoff is how long until we consider this a new scrobble
|
||||
backoff = timezone.now() + timedelta(minutes=VIDEO_BACKOFF)
|
||||
wait_period = timezone.now() + timedelta(days=VIDEO_WAIT_PERIOD)
|
||||
|
||||
return cls.update_or_create(
|
||||
scrobble, backoff, wait_period, jellyfin_data
|
||||
logger.debug(
|
||||
f"No existing scrobble for video {video}, creating",
|
||||
{"scrobble_data": scrobble_data},
|
||||
)
|
||||
# If creating a new scrobble, we don't need status
|
||||
scrobble_data.pop('jellyfin_status')
|
||||
return cls.create(scrobble_data)
|
||||
|
||||
@classmethod
|
||||
def create_or_update_for_track(
|
||||
cls, track: "Track", user_id: int, scrobble_data: dict
|
||||
) -> "Scrobble":
|
||||
"""Look up any existing scrobbles for a track and compare
|
||||
the appropriate backoff time for music tracks to the setting
|
||||
so we can avoid duplicating scrobbles."""
|
||||
scrobble_data['track_id'] = track.id
|
||||
|
||||
scrobble = (
|
||||
cls.objects.filter(track=track, user_id=user_id)
|
||||
cls.objects.filter(
|
||||
track=track,
|
||||
user_id=user_id,
|
||||
played_to_completion=False,
|
||||
)
|
||||
.order_by('-modified')
|
||||
.first()
|
||||
)
|
||||
@ -118,115 +126,109 @@ class Scrobble(TimeStampedModel):
|
||||
f"Found existing scrobble for track {track}, updating",
|
||||
{"scrobble_data": scrobble_data},
|
||||
)
|
||||
return cls.update(scrobble, scrobble_data)
|
||||
|
||||
backoff = timezone.now() + timedelta(seconds=TRACK_BACKOFF)
|
||||
wait_period = timezone.now() + timedelta(minutes=TRACK_WAIT_PERIOD)
|
||||
|
||||
return cls.update_or_create(
|
||||
scrobble, backoff, wait_period, scrobble_data
|
||||
logger.debug(
|
||||
f"No existing scrobble for track {track}, creating",
|
||||
{"scrobble_data": scrobble_data},
|
||||
)
|
||||
# If creating a new scrobble, we don't need status
|
||||
scrobble_data.pop('mopidy_status')
|
||||
return cls.create(scrobble_data)
|
||||
|
||||
@classmethod
|
||||
def create_or_update_for_podcast_episode(
|
||||
cls, episode: "Episode", user_id: int, scrobble_data: dict
|
||||
) -> "Scrobble":
|
||||
scrobble_data['podcast_episode_id'] = episode.id
|
||||
|
||||
scrobble = (
|
||||
cls.objects.filter(podcast_episode=episode, user_id=user_id)
|
||||
cls.objects.filter(
|
||||
podcast_episode=episode,
|
||||
user_id=user_id,
|
||||
played_to_completion=False,
|
||||
)
|
||||
.order_by('-modified')
|
||||
.first()
|
||||
)
|
||||
if scrobble:
|
||||
logger.debug(
|
||||
f"Found existing scrobble for podcast {episode}, updating",
|
||||
{"scrobble_data": scrobble_data},
|
||||
)
|
||||
return cls.update(scrobble, scrobble_data)
|
||||
|
||||
logger.debug(
|
||||
f"Found existing scrobble for podcast {episode}, updating",
|
||||
f"No existing scrobble for podcast epsiode {episode}, creating",
|
||||
{"scrobble_data": scrobble_data},
|
||||
)
|
||||
|
||||
backoff = timezone.now() + timedelta(seconds=TRACK_BACKOFF)
|
||||
wait_period = timezone.now() + timedelta(minutes=TRACK_WAIT_PERIOD)
|
||||
|
||||
return cls.update_or_create(
|
||||
scrobble, backoff, wait_period, scrobble_data
|
||||
)
|
||||
# If creating a new scrobble, we don't need status
|
||||
scrobble_data.pop('mopidy_status')
|
||||
return cls.create(scrobble_data)
|
||||
|
||||
@classmethod
|
||||
def create_or_update_for_sport_event(
|
||||
cls, event: "SportEvent", user_id: int, jellyfin_data: dict
|
||||
cls, event: "SportEvent", user_id: int, scrobble_data: dict
|
||||
) -> "Scrobble":
|
||||
jellyfin_data['sport_event_id'] = event.id
|
||||
scrobble_data['sport_event_id'] = event.id
|
||||
scrobble = (
|
||||
cls.objects.filter(sport_event=event, user_id=user_id)
|
||||
cls.objects.filter(
|
||||
sport_event=event,
|
||||
user_id=user_id,
|
||||
played_to_completion=False,
|
||||
)
|
||||
.order_by('-modified')
|
||||
.first()
|
||||
)
|
||||
if scrobble:
|
||||
logger.debug(
|
||||
f"Found existing scrobble for sport event {event}, updating",
|
||||
{"scrobble_data": scrobble_data},
|
||||
)
|
||||
return cls.update(scrobble, scrobble_data)
|
||||
|
||||
# Backoff is how long until we consider this a new scrobble
|
||||
backoff = timezone.now() + timedelta(minutes=VIDEO_BACKOFF)
|
||||
wait_period = timezone.now() + timedelta(days=VIDEO_WAIT_PERIOD)
|
||||
|
||||
return cls.update_or_create(
|
||||
scrobble, backoff, wait_period, jellyfin_data
|
||||
logger.debug(
|
||||
f"No existing scrobble for sport event {event}, creating",
|
||||
{"scrobble_data": scrobble_data},
|
||||
)
|
||||
# If creating a new scrobble, we don't need status
|
||||
scrobble_data.pop('jellyfin_status')
|
||||
return cls.create(scrobble_data)
|
||||
|
||||
@classmethod
|
||||
def update_or_create(
|
||||
cls,
|
||||
scrobble: Optional["Scrobble"],
|
||||
backoff,
|
||||
wait_period,
|
||||
scrobble_data: dict,
|
||||
) -> Optional["Scrobble"]:
|
||||
|
||||
def update(cls, scrobble: "Scrobble", 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)
|
||||
if not scrobble_status:
|
||||
scrobble_status = scrobble_data.pop('jellyfin_status', None)
|
||||
if not scrobble_status:
|
||||
logger.warning(
|
||||
f"No status update found in message, not scrobbling"
|
||||
)
|
||||
return
|
||||
|
||||
if scrobble:
|
||||
logger.debug(
|
||||
f"Scrobbling to {scrobble} with status {scrobble_status}"
|
||||
)
|
||||
scrobble.update_ticks(scrobble_data)
|
||||
logger.debug(f"Scrobbling to {scrobble} with status {scrobble_status}")
|
||||
scrobble.update_ticks(scrobble_data)
|
||||
|
||||
# On stop, stop progress and send it to the check for completion
|
||||
if scrobble_status == "stopped":
|
||||
return scrobble.stop()
|
||||
# On stop, stop progress and send it to the check for completion
|
||||
if scrobble_status == "stopped":
|
||||
scrobble.stop()
|
||||
# On pause, set is_paused and stop scrobbling
|
||||
if scrobble_status == "paused":
|
||||
scrobble.pause()
|
||||
if scrobble_status == "resumed":
|
||||
scrobble.resume()
|
||||
|
||||
# On pause, set is_paused and stop scrobbling
|
||||
if scrobble_status == "paused":
|
||||
return scrobble.pause()
|
||||
|
||||
if scrobble_status == "resumed":
|
||||
return scrobble.resume()
|
||||
|
||||
for key, value in scrobble_data.items():
|
||||
setattr(scrobble, key, value)
|
||||
scrobble.save()
|
||||
|
||||
# We're not changing the scrobble, but we don't want to walk over an existing one
|
||||
# scrobble_is_finished = (
|
||||
# not scrobble.in_progress and scrobble.modified < backoff
|
||||
# )
|
||||
# if scrobble_is_finished:
|
||||
# logger.info(
|
||||
# 'Found a very recent scrobble for this item, holding off scrobbling again'
|
||||
# )
|
||||
# return
|
||||
check_scrobble_for_finish(scrobble)
|
||||
else:
|
||||
logger.debug(
|
||||
f"Creating new scrobble with status {scrobble_status}"
|
||||
)
|
||||
# If we default this to "" we can probably remove this
|
||||
scrobble_data['scrobble_log'] = ""
|
||||
scrobble = cls.objects.create(
|
||||
**scrobble_data,
|
||||
)
|
||||
for key, value in scrobble_data.items():
|
||||
setattr(scrobble, key, value)
|
||||
scrobble.save()
|
||||
check_scrobble_for_finish(scrobble)
|
||||
return scrobble
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
scrobble_data: dict,
|
||||
) -> "Scrobble":
|
||||
scrobble_data['scrobble_log'] = ""
|
||||
scrobble = cls.objects.create(
|
||||
**scrobble_data,
|
||||
)
|
||||
return scrobble
|
||||
|
||||
def stop(self) -> None:
|
||||
|
||||
@ -86,15 +86,12 @@ def mopidy_scrobble_track(
|
||||
"mopidy_status": data_dict.get("status"),
|
||||
}
|
||||
|
||||
scrobble = None
|
||||
# Jellyfin MB ids suck, so always overwrite with Mopidy if they're offering
|
||||
track.musicbrainz_id = data_dict.get("musicbrainz_track_id")
|
||||
track.save()
|
||||
|
||||
scrobble = Scrobble.create_or_update_for_track(track, user_id, mopidy_data)
|
||||
|
||||
if track:
|
||||
# Jellyfin MB ids suck, so always overwrite with Mopidy if they're offering
|
||||
track.musicbrainz_id = data_dict.get("musicbrainz_track_id")
|
||||
track.save()
|
||||
scrobble = Scrobble.create_or_update_for_track(
|
||||
track, user_id, mopidy_data
|
||||
)
|
||||
return scrobble
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
from urllib.parse import unquote
|
||||
|
||||
from dateutil.parser import ParserError, parse
|
||||
@ -68,14 +67,11 @@ def parse_mopidy_uri(uri: str) -> dict:
|
||||
|
||||
|
||||
def check_scrobble_for_finish(scrobble: "Scrobble") -> None:
|
||||
completion_percent = getattr(settings, "MUSIC_COMPLETION_PERCENT", 95)
|
||||
if scrobble.video:
|
||||
completion_percent = getattr(settings, "VIDEO_COMPLETION_PERCENT", 90)
|
||||
if scrobble.podcast_episode:
|
||||
completion_percent = getattr(
|
||||
settings, "PODCAST_COMPLETION_PERCENT", 25
|
||||
)
|
||||
completion_percent = scrobble.media_obj.COMPLETION_PERCENT
|
||||
|
||||
if scrobble.percent_played >= completion_percent:
|
||||
logger.debug(f"Completion percent {completion_percent} met, finishing")
|
||||
|
||||
scrobble.in_progress = False
|
||||
scrobble.is_paused = False
|
||||
scrobble.played_to_completion = True
|
||||
|
||||
@ -39,18 +39,6 @@ from vrobbler.apps.music.aggregators import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TRUTHY_VALUES = [
|
||||
'true',
|
||||
'1',
|
||||
't',
|
||||
'y',
|
||||
'yes',
|
||||
'yeah',
|
||||
'yup',
|
||||
'certainly',
|
||||
'uh-huh',
|
||||
]
|
||||
|
||||
|
||||
class RecentScrobbleList(ListView):
|
||||
model = Scrobble
|
||||
@ -148,9 +136,7 @@ def jellyfin_websocket(request):
|
||||
if not scrobble:
|
||||
return Response({}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response(
|
||||
{'scrobble_id': scrobble.id}, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response({'scrobble_id': scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@ -171,6 +157,4 @@ def mopidy_websocket(request):
|
||||
if not scrobble:
|
||||
return Response({}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response(
|
||||
{'scrobble_id': scrobble.id}, status=status.HTTP_201_CREATED
|
||||
)
|
||||
return Response({'scrobble_id': scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
@ -2,6 +2,7 @@ import logging
|
||||
from typing import Dict
|
||||
from uuid import uuid4
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -38,6 +39,8 @@ class Team(TimeStampedModel):
|
||||
|
||||
|
||||
class SportEvent(ScrobblableMixin):
|
||||
COMPLETION_PERCENT = getattr(settings, 'SPORT_COMPLETION_PERCENT', 90)
|
||||
|
||||
class Type(models.TextChoices):
|
||||
UNKNOWN = 'UK', _('Unknown')
|
||||
GAME = 'GA', _('Game')
|
||||
|
||||
@ -2,6 +2,7 @@ import logging
|
||||
from typing import Dict
|
||||
from uuid import uuid4
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -32,6 +33,8 @@ class Series(TimeStampedModel):
|
||||
|
||||
|
||||
class Video(ScrobblableMixin):
|
||||
COMPLETION_PERCENT = getattr(settings, 'VIDEO_COMPLETION_PERCENT', 90)
|
||||
|
||||
class VideoType(models.TextChoices):
|
||||
UNKNOWN = 'U', _('Unknown')
|
||||
TV_EPISODE = 'E', _('TV Episode')
|
||||
|
||||
@ -37,10 +37,6 @@ KEEP_DETAILED_SCROBBLE_LOGS = os.getenv(
|
||||
"VROBBLER_KEEP_DETAILED_SCROBBLE_LOGS", False
|
||||
)
|
||||
|
||||
PODCAST_COMPLETION_PERCENT = os.getenv(
|
||||
"VROBBLER_PODCAST_COMPLETION_PERCENT", 25
|
||||
)
|
||||
MUSIC_COMPLETION_PERCENT = os.getenv("VROBBLER_MUSIC_COMPLETION_PERCENT", 90)
|
||||
|
||||
# Should we cull old in-progress scrobbles that are beyond the wait period for resuming?
|
||||
DELETE_STALE_SCROBBLES = os.getenv("VROBBLER_DELETE_STALE_SCROBBLES", True)
|
||||
@ -48,15 +44,6 @@ DELETE_STALE_SCROBBLES = os.getenv("VROBBLER_DELETE_STALE_SCROBBLES", True)
|
||||
# Used to dump data coming from srobbling sources, helpful for building new inputs
|
||||
DUMP_REQUEST_DATA = os.getenv("VROBBLER_DUMP_REQUEST_DATA", False)
|
||||
|
||||
VIDEO_BACKOFF_MINUTES = os.getenv("VROBBLER_VIDEO_BACKOFF_MINUTES", 15)
|
||||
MUSIC_BACKOFF_SECONDS = os.getenv("VROBBLER_VIDEO_BACKOFF_SECONDS", 1)
|
||||
|
||||
# If you stop waching or listening to a track, how long should we wait before we
|
||||
# give up on the old scrobble and start a new one? This could also be considered
|
||||
# a "continue in progress scrobble" time period. So if you pause the media and
|
||||
# start again, should it be a new scrobble.
|
||||
VIDEO_WAIT_PERIOD_DAYS = os.getenv("VROBBLER_VIDEO_WAIT_PERIOD_DAYS", 1)
|
||||
MUSIC_WAIT_PERIOD_MINUTES = os.getenv("VROBBLER_VIDEO_BACKOFF_MINUTES", 1)
|
||||
|
||||
THESPORTSDB_API_KEY = os.getenv("VROBBLER_THESPORTSDB_API_KEY", "2")
|
||||
THESPORTSDB_BASE_URL = os.getenv(
|
||||
@ -288,17 +275,17 @@ LOGGING = {
|
||||
"class": "logging.NullHandler",
|
||||
"level": LOG_LEVEL,
|
||||
},
|
||||
"file": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": "".join([LOG_FILE_PATH, "vrobbler.log"]),
|
||||
"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,
|
||||
},
|
||||
"requests_file": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": "".join([LOG_FILE_PATH, "vrobbler_requests.log"]),
|
||||
"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": {
|
||||
@ -310,12 +297,13 @@ LOGGING = {
|
||||
"django.db.backends": {"handlers": ["null"]},
|
||||
"django.server": {"handlers": ["null"]},
|
||||
"vrobbler": {
|
||||
"handlers": ["console", "file"],
|
||||
"handlers": ["file"],
|
||||
"propagate": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if DEBUG:
|
||||
# We clear out a db with lots of games all the time in dev
|
||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 3000
|
||||
LOG_TO_CONSOLE = os.getenv("VROBBLER_LOG_TO_CONSOLE", False)
|
||||
if LOG_TO_CONSOLE:
|
||||
LOGGING['loggers']['django']['handlers'] = ["console"]
|
||||
LOGGING['loggers']['vrobbler']['handlers'] = ["console"]
|
||||
|
||||
BIN
vrobbler/static/images/apple-touch-icon.png
Normal file
BIN
vrobbler/static/images/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
vrobbler/static/images/favicon.ico
Normal file
BIN
vrobbler/static/images/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@ -8,6 +8,7 @@
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
|
||||
@ -165,8 +166,7 @@
|
||||
</style>
|
||||
{% block head_extra %}{% endblock %}
|
||||
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<!-- Place favicon.ico in the root directory -->
|
||||
<link rel="apple-touch-icon" href="{% static 'images/apple-touch-icon.png' %}">
|
||||
</head>
|
||||
<body>
|
||||
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
|
||||
|
||||
Reference in New Issue
Block a user