[scrobbles] Fix log data parsing for tasks and boardgames

Add pagination to task and board game detail pages
This commit is contained in:
2025-07-30 11:37:57 -04:00
parent a7551ef162
commit 9437fdba60
6 changed files with 96 additions and 22 deletions

View File

@ -7,6 +7,7 @@ from typing import Optional
from dataclass_wizard import JSONWizard from dataclass_wizard import JSONWizard
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from locations.models import GeoLocation from locations.models import GeoLocation
from people.models import Person
User = get_user_model() User = get_user_model()
@ -66,8 +67,7 @@ class WithOthersLogData(JSONDataclass):
@dataclass @dataclass
class BoardGameScoreLogData(JSONDataclass): class BoardGameScoreLogData(JSONDataclass):
user_id: Optional[int] = None person_id: Optional[int] = None
name_str: str = ""
bgg_username: str = "" bgg_username: str = ""
color: Optional[str] = None color: Optional[str] = None
character: Optional[str] = None character: Optional[str] = None
@ -75,19 +75,19 @@ class BoardGameScoreLogData(JSONDataclass):
score: Optional[int] = None score: Optional[int] = None
win: Optional[bool] = None win: Optional[bool] = None
new: Optional[bool] = None new: Optional[bool] = None
rank: Optional[int] = None
seat_order: Optional[int] = None
role: Optional[str] = None
@property @property
def user(self) -> Optional[User]: def person(self) -> Optional[Person]:
user = None return Person.objects.filter(id=self.person_id).first()
if self.user_id:
user = User.objects.filter(id=self.user_id).first()
return user
@property @property
def name(self) -> str: def name(self) -> str:
name = self.name_str name = ""
if self.user_id: if self.person:
name = self.user.first_name name = self.person.name
return name return name
def __str__(self) -> str: def __str__(self) -> str:
@ -106,17 +106,20 @@ class BoardGameLogData(LongPlayLogData):
serial_scrobble_id: Optional[int] = None serial_scrobble_id: Optional[int] = None
long_play_complete: Optional[bool] = None long_play_complete: Optional[bool] = None
players: Optional[list[BoardGameScoreLogData]] = None players: Optional[list[BoardGameScoreLogData]] = None
location: Optional[str] = None location_id: Optional[int] = None
geo_location_id: Optional[int] = None
difficulty: Optional[int] = None difficulty: Optional[int] = None
solo: Optional[bool] = None solo: Optional[bool] = None
two_handed: Optional[bool] = None two_handed: Optional[bool] = None
expansion_ids: Optional[int] = None
@cached_property @cached_property
def geo_location(self) -> Optional[GeoLocation]: def geo_location(self) -> Optional[GeoLocation]:
if self.geo_location_id: if self.geo_location_id:
return GeoLocation.objects.filter(id=self.geo_location_id).first() return GeoLocation.objects.filter(id=self.geo_location_id).first()
@cached_property
def player_log(self) -> str:
return ", ".join([BoardGameScoreLogData(**player).__str__() for player in self.players])
@dataclass @dataclass
class BookPageLogData(JSONDataclass): class BookPageLogData(JSONDataclass):

View File

@ -736,7 +736,7 @@ class Scrobble(TimeStampedModel):
if not log_dict: if not log_dict:
log_dict = {} log_dict = {}
return logdata_cls.from_dict(log_dict) return logdata_cls(**log_dict)
def redirect_url(self, user_id) -> str: def redirect_url(self, user_id) -> str:
user = User.objects.filter(id=user_id).first() user = User.objects.filter(id=user_id).first()

View File

@ -19,6 +19,8 @@ from django.views.generic import DetailView, FormView, TemplateView
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from django.views.generic.list import ListView from django.views.generic.list import ListView
from music.aggregators import live_charts, scrobble_counts, week_of_scrobbles from music.aggregators import live_charts, scrobble_counts, week_of_scrobbles
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from rest_framework import status from rest_framework import status
from rest_framework.decorators import ( from rest_framework.decorators import (
api_view, api_view,
@ -76,18 +78,34 @@ class ScrobbleableListView(ListView):
) )
return queryset return queryset
class ScrobbleableDetailView(DetailView): class ScrobbleableDetailView(DetailView):
model = None model = None
slug_field = "uuid" slug_field = "uuid"
paginate_by = 200 # You can set this to whatever page size you want
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs) context_data = super().get_context_data(**kwargs)
context_data["scrobbles"] = list() scrobbles = []
if not self.request.user.is_anonymous: if not self.request.user.is_anonymous:
context_data["scrobbles"] = self.object.scrobble_set.filter( scrobbles = self.object.scrobble_set.filter(
user=self.request.user user=self.request.user
).order_by("-timestamp") ).order_by("-timestamp")
paginator = Paginator(scrobbles, self.paginate_by)
page_number = self.request.GET.get("page")
try:
page_obj = paginator.page(page_number)
except PageNotAnInteger:
page_obj = paginator.page(1)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)
context_data["page_obj"] = page_obj
context_data["scrobbles"] = page_obj.object_list
context_data["is_paginated"] = paginator.num_pages > 1
return context_data return context_data

View File

@ -23,6 +23,16 @@ class TaskLogData(JSONDataclass):
todoist_type: Optional[str] = None todoist_type: Optional[str] = None
notes: Optional[dict] = None notes: Optional[dict] = None
def notes_as_str(self) -> str:
"""Return formatted notes with line breaks and no keys"""
note_block = ""
if not self.notes:
return note_block
for id, content in self.notes.items():
note_block += content + "</br>"
return note_block
class Task(LongPlayScrobblableMixin): class Task(LongPlayScrobblableMixin):
"""Basically a holder for Todoist Tasks """Basically a holder for Todoist Tasks
@ -42,9 +52,9 @@ class Task(LongPlayScrobblableMixin):
def strings(self) -> ScrobblableConstants: def strings(self) -> ScrobblableConstants:
return ScrobblableConstants(verb="Doing", tags="memo") return ScrobblableConstants(verb="Doing", tags="memo")
# @property @property
# def logdata_cls(self): def logdata_cls(self):
# return TaskLogData return TaskLogData
def source_url_for_user(self, user_id) -> str: def source_url_for_user(self, user_id) -> str:
url = "" url = ""

View File

@ -56,7 +56,7 @@
<tr> <tr>
<th scope="col">Date</th> <th scope="col">Date</th>
<th scope="col">Publisher</th> <th scope="col">Publisher</th>
<th scope="col">Screenshot</th> <th scope="col">Players</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -64,12 +64,32 @@
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
<td>{{scrobble.media_obj.publisher}}</td> <td>{{scrobble.media_obj.publisher}}</td>
<td>{% if scrobble.screenshot%}<img src="{{scrobble.screenshot.url}}" width=250 />{% endif %}</td> <td>{% if scrobble.logdata.player_log %}{{scrobble.logdata.player_log}}{% else %}No data{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">&laquo; Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if num == page_obj.number %}
<strong>{{ num }}</strong>
{% else %}
<a href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next &raquo;</a>
{% endif %}
</div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -22,6 +22,7 @@
width: 600px; width: 600px;
margin-left: 10px; margin-left: 10px;
} }
.pagination a { padding: 0 5px 0 5px; }
</style> </style>
{% endblock %} {% endblock %}
@ -47,12 +48,14 @@
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
<h3>Last scrobbles</h3> <h3>Last scrobbles</h3>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
<tr> <tr>
<th scope="col">Date</th> <th scope="col">Date</th>
<th scope="col">Description</th> <th scope="col">Description</th>
<th scope="col">Notes</th>
<th scope="col">Source</th> <th scope="col">Source</th>
</tr> </tr>
</thead> </thead>
@ -61,13 +64,33 @@
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
<td><a href="{{scrobble.get_media_source_url}}">{{scrobble.logdata.description}}</a></td> <td><a href="{{scrobble.get_media_source_url}}">{{scrobble.logdata.description}}</a></td>
<td>{{scrobble.logdata.notes_as_str|safe}}</td>
<td>{{scrobble.source}}</td> <td>{{scrobble.source}}</td>
<td>{{scrobble.log.notes}}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">&laquo; Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if num == page_obj.number %}
<strong>{{ num }}</strong>
{% else %}
<a href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next &raquo;</a>
{% endif %}
</div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}