[scrobbles] Fix mood starting, and clean up code

This commit is contained in:
2024-08-10 17:41:26 -04:00
parent f3fc58e2c0
commit 0ce19527a2
9 changed files with 205 additions and 17 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.13 on 2024-08-10 20:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("moods", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="mood",
name="image",
field=models.ImageField(blank=True, null=True, upload_to="moods/"),
),
]

View File

@ -1,10 +1,12 @@
import logging import logging
from uuid import uuid4
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit
from scrobbles.mixins import ScrobblableMixin from scrobbles.mixins import ScrobblableMixin
from vrobbler.apps.scrobbles.dataclasses import MoodLogData from vrobbler.apps.scrobbles.dataclasses import MoodLogData
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -14,6 +16,19 @@ User = get_user_model()
class Mood(ScrobblableMixin): class Mood(ScrobblableMixin):
description = models.TextField(**BNULL) description = models.TextField(**BNULL)
image = models.ImageField(upload_to="moods/", **BNULL)
image_small = ImageSpecField(
source="thumbnail",
processors=[ResizeToFit(100, 100)],
format="JPEG",
options={"quality": 60},
)
image_medium = ImageSpecField(
source="thumbnail",
processors=[ResizeToFit(300, 300)],
format="JPEG",
options={"quality": 75},
)
def __str__(self): def __str__(self):
if self.title: if self.title:
@ -23,6 +38,19 @@ class Mood(ScrobblableMixin):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("moods:mood-detail", kwargs={"slug": self.uuid}) return reverse("moods:mood-detail", kwargs={"slug": self.uuid})
def get_start_url(self):
return reverse("scrobbles:start", kwargs={"uuid": self.uuid})
@property
def subtitle(self) -> str:
return ""
@property @property
def logdata_cls(self): def logdata_cls(self):
return MoodLogData return MoodLogData
@property
def primary_image_url(self) -> str:
if self.image:
return self.image.url
return ""

View File

@ -12,6 +12,7 @@ PLAY_AGAIN_MEDIA = {
"videogames": "VideoGame", "videogames": "VideoGame",
"books": "Book", "books": "Book",
"boardgames": "BoardGame", "boardgames": "BoardGame",
"moods": "Mood",
} }
MEDIA_END_PADDING_SECONDS = { MEDIA_END_PADDING_SECONDS = {

View File

@ -2,12 +2,14 @@ import logging
from typing import Optional from typing import Optional
from uuid import uuid4 from uuid import uuid4
from django.apps import apps
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from django_extensions.db.models import TimeStampedModel from django_extensions.db.models import TimeStampedModel
from taggit.managers import TaggableManager
from scrobbles.utils import get_scrobbles_for_media from scrobbles.utils import get_scrobbles_for_media
from taggit.models import TagBase, GenericTaggedItemBase from taggit.managers import TaggableManager
from taggit.models import GenericTaggedItemBase, TagBase
BNULL = {"blank": True, "null": True} BNULL = {"blank": True, "null": True}
@ -33,6 +35,7 @@ class ObjectWithGenres(GenericTaggedItemBase):
class ScrobblableMixin(TimeStampedModel): class ScrobblableMixin(TimeStampedModel):
SECONDS_TO_STALE = 1600 SECONDS_TO_STALE = 1600
COMPLETION_PERCENT = 100
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL) uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
title = models.CharField(max_length=255, **BNULL) title = models.CharField(max_length=255, **BNULL)
@ -44,21 +47,53 @@ class ScrobblableMixin(TimeStampedModel):
class Meta: class Meta:
abstract = True abstract = True
def basic_scrobble_data(self, user_id) -> dict:
return {
"user_id": user_id,
"timestamp": timezone.now(),
"playback_position_seconds": 0,
"source": "Vrobbler",
}
def scrobble_for_user(self, user_id):
Scrobble = apps.get_model("scrobbles", "Scrobble")
scrobble_data = self.basic_scrobble_data(user_id)
logger.info(
"[scrobble_for_user] called",
extra={
"webpage_id": self.id,
"user_id": user_id,
"scrobble_data": scrobble_data,
"media_type": Scrobble.MediaType.WEBPAGE,
},
)
return Scrobble.create_or_update(self, user_id, scrobble_data)
@property @property
def primary_image_url(self) -> str: def primary_image_url(self) -> str:
logger.warn(f"Not implemented yet") logger.warn(f"Not implemented yet")
return "" return ""
def fix_metadata(self): @property
def logdata_cls(self) -> None:
logger.warn("logdata_cls() not implemented yet")
return None
@property
def subtitle(self) -> str:
return ""
def fix_metadata(self) -> None:
logger.warn("fix_metadata() not implemented yet") logger.warn("fix_metadata() not implemented yet")
@classmethod @classmethod
def find_or_create(cls): def find_or_create(cls) -> None:
logger.warn("find_or_create() not implemented yet") logger.warn("find_or_create() not implemented yet")
def scrobble(self, user_id, **kwargs): def scrobble(self, user_id, **kwargs):
"""Given a user ID and a dictionary of data, attempts to scrobble it""" """Given a user ID and a dictionary of data, attempts to scrobble it"""
from scrobbles.models import Scrobble from scrobbles.models import Scrobble
Scrobble.create_or_update(self, user_id, **kwargs) Scrobble.create_or_update(self, user_id, **kwargs)

View File

@ -904,7 +904,7 @@ class Scrobble(TimeStampedModel):
.order_by("-timestamp") .order_by("-timestamp")
.first() .first()
) )
source = scrobble_data.get("source") source = scrobble_data.get("source", "Vrobbler")
mtype = media.__class__.__name__ mtype = media.__class__.__name__
mopidy_status = scrobble_data.get("mopidy_status", None) mopidy_status = scrobble_data.get("mopidy_status", None)

View File

@ -69,12 +69,13 @@ class ScrobbleableListView(ListView):
if not self.request.user.is_anonymous: if not self.request.user.is_anonymous:
queryset = queryset.annotate( queryset = queryset.annotate(
scrobble_count=Count("scrobble"), scrobble_count=Count("scrobble"),
filter=Q(user=self.request.user), filter=Q(scrobble__user=self.request.user),
).order_by("-scrobble_count") ).order_by("-scrobble_count")
else: else:
queryset = queryset.annotate( queryset = queryset.annotate(
scrobble_count=Count("scrobble") scrobble_count=Count("scrobble")
).order_by("-scrobble_count") ).order_by("-scrobble_count")
return queryset
class ScrobbleableDetailView(DetailView): class ScrobbleableDetailView(DetailView):
@ -85,8 +86,8 @@ class ScrobbleableDetailView(DetailView):
context_data = super().get_context_data(**kwargs) context_data = super().get_context_data(**kwargs)
context_data["scrobbles"] = list() context_data["scrobbles"] = list()
if not self.request.user.is_anonymous: if not self.request.user.is_anonymous:
context_data["scrobbles"] = self.object.scrobbles( context_data["scrobbles"] = self.object.scrobble_set.filter(
self.request.user user=self.request.user
) )
return context_data return context_data
@ -429,6 +430,10 @@ def import_audioscrobbler_file(request):
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@api_view(["GET"]) @api_view(["GET"])
def scrobble_start(request, uuid): def scrobble_start(request, uuid):
logger.info(
"[scrobble_start] called",
extra={"request": request, "uuid": uuid},
)
user = request.user user = request.user
success_url = request.META.get("HTTP_REFERER") success_url = request.META.get("HTTP_REFERER")
@ -443,19 +448,17 @@ def scrobble_start(request, uuid):
break break
if not media_obj: if not media_obj:
logger.info(
"[scrobble_start] media object not found",
extra={"uuid": uuid, "user_id": user.id},
)
# TODO Log that we couldn't find a media obj to scrobble
return return
scrobble = None scrobble = None
user_id = request.user.id user_id = request.user.id
if media_obj: if media_obj:
if media_obj.__class__.__name__ == Scrobble.MediaType.BOOK: media_obj.scrobble_for_user(user_id)
scrobble = manual_scrobble_book(media_obj.openlibrary_id, user_id)
if media_obj.__class__.__name__ == Scrobble.MediaType.VIDEO_GAME:
scrobble = manual_scrobble_video_game(media_obj.hltb_id, user_id)
if media_obj.__class__.__name__ == Scrobble.MediaType.BOARD_GAME:
scrobble = manual_scrobble_board_game(media_obj.bggeek_id, user_id)
if media_obj.__class__.__name__ == Scrobble.MediaType.WEBPAGE:
scrobble = manual_scrobble_webpage(media_obj.url, user_id)
if scrobble: if scrobble:
messages.add_message( messages.add_message(

View File

@ -89,6 +89,28 @@ class WebPage(ScrobblableMixin):
def subtitle(self): def subtitle(self):
return self.domain return self.domain
@property
def primary_image_url(self) -> str:
# TODO Figure out how to add a preview?
return ""
def scrobble_for_user(self, user_id):
Scrobble = apps.get_model("scrobbles", "Scrobble")
scrobble_data = self.basic_scrobble_data(user_id)
logger.info(
"[scrobble_for_user] called for webpage",
extra={
"webpage_id": self.id,
"user_id": user_id,
"scrobble_data": scrobble_data,
"media_type": Scrobble.MediaType.WEBPAGE,
},
)
scrobble = Scrobble.create_or_update(self, user_id, scrobble_data)
# TODO Possibly make this async?
scrobble.push_to_archivebox()
return scrobble
def scrobbles(self, user): def scrobbles(self, user):
Scrobble = apps.get_model("scrobbles", "Scrobble") Scrobble = apps.get_model("scrobbles", "Scrobble")
return Scrobble.objects.filter(user=user, web_page=self).order_by( return Scrobble.objects.filter(user=user, web_page=self).order_by(

View File

@ -0,0 +1,39 @@
{% extends "base_list.html" %}
{% load static %}
{% block title %}{{object.title}}{% endblock %}
{% block lists %}
<div class="row webpage">
<div class="webpage-metadata">
<p>{{object.description}}</p>
</div>
{% if object.extract %}
<div class="webpage-body" id="article">
{{object.extract|linebreaks}}
</div>
{% endif %}
<hr/>
</div>
<div class="row">
<div class="col-md">
<h3>Last scrobbles</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Date</th>
</tr>
</thead>
<tbody>
{% for scrobble in object.scrobble_set.all %}
<tr>
<td>{{scrobble.timestamp}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,42 @@
{% extends "base_list.html" %}
{% block title %}Moods{% endblock %}
{% block head_extra %}
<style>
dl { width: 210px; float:left; margin-right: 10px; }
dt a { color:white; text-decoration: none; font-size:smaller; }
img { height:200px; width: 200px; object-fit: cover; }
dd .right { float:right; }
</style>
{% endblock %}
{% block lists %}
<div class="row">
<div class="col-md">
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Scrobbles</th>
<th scope="col">Start</th>
</tr>
</thead>
<tbody>
{% for mood in object_list %}
<tr>
<td><a href="{{mood.get_absolute_url}}">{{mood}}</a></td>
{% if request.user.is_authenticated %}
<td>{{mood.scrobble_count}}</td>
<td><a type="button" class="btn btn-sm btn-primary" href="{{mood.get_start_url}}">Scrobble</a></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}