Remove concurrent-reading trend, replace with concurrent-media-listening
This commit is contained in:
@ -561,11 +561,13 @@ def test_scrobble_detail_view_with_notes_and_labels(client):
|
||||
username="testuser", email="test@example.com", password="testpass"
|
||||
)
|
||||
task = Task.objects.create(title="Test Task", description="Test description")
|
||||
from django.utils import timezone
|
||||
scrobble = Scrobble.objects.create(
|
||||
task=task,
|
||||
media_type="Task",
|
||||
user=user,
|
||||
visibility="public",
|
||||
timestamp=timezone.now(),
|
||||
log={
|
||||
"notes": [
|
||||
{"2024-01-01 10:00:00": "Note with label"},
|
||||
@ -574,6 +576,7 @@ def test_scrobble_detail_view_with_notes_and_labels(client):
|
||||
"description": "Test description",
|
||||
},
|
||||
)
|
||||
client.login(username="testuser", password="testpass")
|
||||
url = reverse("scrobbles:detail", kwargs={"pk": scrobble.id})
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
<div class="row">
|
||||
{% for category, info in data.items %}
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
{% if category == "Trails" %}🥾
|
||||
{% elif category == "Books" %}📖
|
||||
{% elif category == "Foods" %}🍔
|
||||
{% elif category == "Drinks" %}🍺
|
||||
{% elif category == "Tasks" %}✅
|
||||
{% elif category == "Board Games" %}🎲
|
||||
{% elif category == "Video Games" %}🎮
|
||||
{% else %}🎧
|
||||
{% endif %}
|
||||
{{ category }}
|
||||
</h5>
|
||||
<p class="text-muted small">{{ info.total_anchor_sessions }} session{{ info.total_anchor_sessions|pluralize }}</p>
|
||||
{% if info.artists %}
|
||||
<table class="table table-sm table-borderless mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Artist</th>
|
||||
<th class="text-end">Plays</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for artist in info.artists %}
|
||||
<tr>
|
||||
<td>{{ artist.artist_name }}</td>
|
||||
<td class="text-end">{{ artist.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted mb-0">No concurrent listening data.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12">
|
||||
<p class="text-muted">No concurrent listening data found.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@ -1,42 +0,0 @@
|
||||
<div class="row">
|
||||
{% if data.books %}
|
||||
{% for book in data.books %}
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body py-2">
|
||||
<h6 class="card-title mb-1">
|
||||
{% if book.book_uuid %}
|
||||
<a href="{% url 'books:book_detail' book.book_uuid %}">{{ book.book_title }}</a>
|
||||
{% else %}
|
||||
{{ book.book_title }}
|
||||
{% endif %}
|
||||
<small class="text-muted">({{ book.total_sessions }} listening sessions)</small>
|
||||
</h6>
|
||||
{% if book.tracks %}
|
||||
<table class="table table-sm table-borderless mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Track</th>
|
||||
<th>Artist</th>
|
||||
<th class="text-end">Plays</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for t in book.tracks %}
|
||||
<tr>
|
||||
<td>{% if t.track_uuid %}<a href="{% url 'music:track_detail' t.track_uuid %}">{{ t.track_name }}</a>{% else %}{{ t.track_name }}{% endif %}</td>
|
||||
<td>{{ t.artist_name }}</td>
|
||||
<td class="text-end">{{ t.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-muted">No concurrent reading data found.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -61,8 +61,8 @@
|
||||
{% elif trend.slug == "concurrent-listening" %}
|
||||
{% include "trends/_concurrent_listening.html" %}
|
||||
|
||||
{% elif trend.slug == "concurrent-reading" %}
|
||||
{% include "trends/_concurrent_reading.html" %}
|
||||
{% elif trend.slug == "concurrent-media-listening" %}
|
||||
{% include "trends/_concurrent_media_listening.html" %}
|
||||
|
||||
{% elif trend.slug == "reading-pace-vs-activity" %}
|
||||
{% include "trends/_reading_pace.html" %}
|
||||
|
||||
@ -5,7 +5,7 @@ from trends.trends.activity import (
|
||||
)
|
||||
from trends.trends.concurrent import (
|
||||
compute_concurrent_listening,
|
||||
compute_concurrent_reading,
|
||||
compute_concurrent_media_listening,
|
||||
)
|
||||
from trends.trends.fasting import compute_fasting
|
||||
from trends.trends.mood import (
|
||||
@ -36,7 +36,9 @@ compute_activity_distribution = register("activity-distribution")(
|
||||
# compute_concurrent_listening = register("concurrent-listening")(
|
||||
# compute_concurrent_listening
|
||||
# )
|
||||
compute_concurrent_reading = register("concurrent-reading")(compute_concurrent_reading)
|
||||
compute_concurrent_media_listening = register("concurrent-media-listening")(
|
||||
compute_concurrent_media_listening
|
||||
)
|
||||
compute_mood_by_time = register("mood-by-time")(compute_mood_by_time)
|
||||
compute_mood_distribution = register("mood-distribution")(compute_mood_distribution)
|
||||
compute_mood_streaks = register("mood-streaks")(compute_mood_streaks)
|
||||
|
||||
@ -35,6 +35,20 @@ def _find_concurrent(anchor_scrobbles, paired_scrobbles):
|
||||
return anchor_to_paired
|
||||
|
||||
|
||||
CONCURRENT_MEDIA_TYPES = {
|
||||
"Trail": "Trails",
|
||||
"Book": "Books",
|
||||
"Food": "Foods",
|
||||
"Beer": "Drinks",
|
||||
"Wine": "Drinks",
|
||||
"Coffee": "Drinks",
|
||||
"Drink": "Drinks",
|
||||
"Task": "Tasks",
|
||||
"BoardGame": "Board Games",
|
||||
"VideoGame": "Video Games",
|
||||
}
|
||||
|
||||
|
||||
def _get_media_name(scrobble):
|
||||
"""Return the name of the media object associated with a scrobble."""
|
||||
for attr in [
|
||||
@ -173,11 +187,11 @@ def compute_concurrent_listening(user, period="all_time"):
|
||||
}
|
||||
|
||||
|
||||
def compute_concurrent_reading(user, period="all_time"):
|
||||
"""Find what music was listened to while reading books.
|
||||
def compute_concurrent_media_listening(user, period="last_30"):
|
||||
"""Find what music (by artist) you listen to while doing other activities.
|
||||
|
||||
Returns a dict with key 'books' containing a list of entries with the
|
||||
book title and the tracks listened to while reading.
|
||||
Groups concurrent listening by media type (Trails, Books, Foods, Drinks,
|
||||
Tasks, Board Games, Video Games) and shows the top 3 artists per type.
|
||||
"""
|
||||
from trends.utils import get_date_range
|
||||
|
||||
@ -188,15 +202,15 @@ def compute_concurrent_reading(user, period="all_time"):
|
||||
if end:
|
||||
base_filters &= Q(timestamp__lte=end)
|
||||
|
||||
anchor_media_types = list(CONCURRENT_MEDIA_TYPES.keys())
|
||||
|
||||
anchor_scrobbles = list(
|
||||
Scrobble.objects.filter(
|
||||
base_filters,
|
||||
media_type="Book",
|
||||
media_type__in=anchor_media_types,
|
||||
stop_timestamp__isnull=False,
|
||||
played_to_completion=True,
|
||||
)
|
||||
.select_related("book")
|
||||
.order_by("-timestamp")
|
||||
).order_by("-timestamp")
|
||||
)
|
||||
|
||||
paired_scrobbles = list(
|
||||
@ -206,70 +220,49 @@ def compute_concurrent_reading(user, period="all_time"):
|
||||
stop_timestamp__isnull=False,
|
||||
played_to_completion=True,
|
||||
)
|
||||
.select_related("track")
|
||||
.select_related("track", "track__artist_fk")
|
||||
.order_by("-timestamp")
|
||||
)
|
||||
|
||||
if not anchor_scrobbles or not paired_scrobbles:
|
||||
return {"books": []}
|
||||
return {slug: [] for slug in set(CONCURRENT_MEDIA_TYPES.values())}
|
||||
|
||||
anchor_to_paired = _find_concurrent(anchor_scrobbles, paired_scrobbles)
|
||||
paired_by_pk = {s.pk: s for s in paired_scrobbles}
|
||||
|
||||
books_by_uuid = {}
|
||||
media_type_artists = defaultdict(lambda: defaultdict(int))
|
||||
|
||||
for anchor in anchor_scrobbles:
|
||||
paired_pks = anchor_to_paired.get(anchor.pk, [])
|
||||
if not paired_pks:
|
||||
continue
|
||||
|
||||
book = anchor.book
|
||||
book_uuid = str(book.uuid) if book and book.uuid else ""
|
||||
book_key = book_uuid or str(book) if book else "Unknown"
|
||||
|
||||
if book_key not in books_by_uuid:
|
||||
books_by_uuid[book_key] = {
|
||||
"book_title": str(book) if book else "Unknown",
|
||||
"book_uuid": book_uuid,
|
||||
"total_sessions": 0,
|
||||
"tracks_by_name": defaultdict(int),
|
||||
"track_details": {},
|
||||
}
|
||||
|
||||
books_by_uuid[book_key]["total_sessions"] += len(paired_pks)
|
||||
|
||||
for p_pk in paired_pks:
|
||||
ps = paired_by_pk[p_pk]
|
||||
track = ps.track
|
||||
if track is None:
|
||||
continue
|
||||
name = str(track)
|
||||
books_by_uuid[book_key]["tracks_by_name"][name] += 1
|
||||
if name not in books_by_uuid[book_key]["track_details"]:
|
||||
books_by_uuid[book_key]["track_details"][name] = {
|
||||
"track_name": name,
|
||||
"track_uuid": str(track.uuid) if track.uuid else "",
|
||||
"artist_name": str(track.artist) if track.artist else "",
|
||||
}
|
||||
artist = track.artist_fk
|
||||
artist_name = str(artist) if artist else "Unknown"
|
||||
category = CONCURRENT_MEDIA_TYPES.get(anchor.media_type, anchor.media_type)
|
||||
media_type_artists[category][artist_name] += 1
|
||||
|
||||
books = []
|
||||
for bd in books_by_uuid.values():
|
||||
books.append(
|
||||
{
|
||||
"book_title": bd["book_title"],
|
||||
"book_uuid": bd["book_uuid"],
|
||||
"total_sessions": bd["total_sessions"],
|
||||
"tracks": sorted(
|
||||
[
|
||||
{**bd["track_details"][name], "count": count}
|
||||
for name, count in bd["tracks_by_name"].items()
|
||||
],
|
||||
key=lambda x: x["count"],
|
||||
reverse=True,
|
||||
)[:5],
|
||||
}
|
||||
result = {}
|
||||
for category, artists in sorted(media_type_artists.items()):
|
||||
sorted_artists = sorted(
|
||||
[{"artist_name": name, "count": count} for name, count in artists.items()],
|
||||
key=lambda x: x["count"],
|
||||
reverse=True,
|
||||
)[:3]
|
||||
|
||||
anchor_count = sum(
|
||||
1
|
||||
for a in anchor_scrobbles
|
||||
if CONCURRENT_MEDIA_TYPES.get(a.media_type, a.media_type) == category
|
||||
)
|
||||
result[category] = {
|
||||
"artists": sorted_artists,
|
||||
"total_anchor_sessions": anchor_count,
|
||||
}
|
||||
|
||||
return {
|
||||
"books": sorted(books, key=lambda x: x["total_sessions"], reverse=True)[:20],
|
||||
}
|
||||
return result
|
||||
|
||||
@ -18,7 +18,6 @@ PERIOD_LABELS = dict(PERIOD_CHOICES)
|
||||
|
||||
TIME_BOUND_TRENDS = {
|
||||
"activity-distribution",
|
||||
"concurrent-reading",
|
||||
"concurrent-listening",
|
||||
"fasting",
|
||||
"mood-by-time",
|
||||
|
||||
@ -18,10 +18,10 @@ TREND_METADATA = {
|
||||
"description": "What music were you listening to while on trails or at locations?",
|
||||
"icon": "🎧",
|
||||
},
|
||||
"concurrent-reading": {
|
||||
"title": "Concurrent Reading",
|
||||
"description": "What music did you listen to while reading books?",
|
||||
"icon": "📖",
|
||||
"concurrent-media-listening": {
|
||||
"title": "Concurrent Media Listening",
|
||||
"description": "What are you listening to while doing other things?",
|
||||
"icon": "🎧",
|
||||
},
|
||||
"mood-trajectory": {
|
||||
"title": "Mood Trajectory",
|
||||
|
||||
Reference in New Issue
Block a user