Compare commits

...

21 Commits
24 ... 26

Author SHA1 Message Date
6306390f82 [release] 26 2025-09-11 18:58:48 -04:00
350d3ceb14 [templates] Move moods around 2025-09-11 18:56:34 -04:00
a1ff82bfec [templates] Cleaning up templates and datalog forms 2025-09-11 18:55:45 -04:00
92c0c668b3 [locations] Add locations to dashboard 2025-09-11 18:29:28 -04:00
3b77feda45 [templates] Add links to scrobbles
Ultimately these should probably be templated
2025-09-11 18:03:35 -04:00
45c402f8c1 [music] Fix bug in generating log data form 2025-09-11 17:57:17 -04:00
90a1398438 [foods] Adjust fields on food data log 2025-09-11 10:44:48 -04:00
c7a81802ac [release] 25.0 2025-09-11 09:45:02 -04:00
a9a8678ac0 [project] Update toods 2025-09-11 09:43:23 -04:00
cbf0583871 [foods] Add calories to food model 2025-09-11 09:41:22 -04:00
5cac1fe109 [templates] Fix food templates and such 2025-09-11 09:41:12 -04:00
6782ed312d [templates] Add food to homepage 2025-09-11 09:33:34 -04:00
fda505ea4e [scrobbles] Fix calc of elapsed time 2025-09-11 09:33:29 -04:00
8db111f66f [food] Fix lookup for food object 2025-09-11 09:27:32 -04:00
ee1cae496a [music] Back and forth ... let us not use album name 2025-09-11 09:08:14 -04:00
9403c68184 [videos] Fix small bug in views 2025-09-11 09:06:07 -04:00
96030f4a99 [boardgames] Fix expansion checking 2025-09-11 09:05:50 -04:00
a8c3925af4 [project] Check off another task 2025-08-20 11:40:18 -04:00
a2f507a976 [videos] Wire up generic video list view 2025-08-20 11:39:27 -04:00
7a7edc6e47 [templates] Fix some bugs and clean up list views 2025-08-20 11:27:10 -04:00
af6c39fb85 [templates] Clean up task titles 2025-08-19 12:37:26 -04:00
42 changed files with 330 additions and 141 deletions

View File

@ -79,7 +79,7 @@ fetching and simple saving.
:LOGBOOK: :LOGBOOK:
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20 CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
:END: :END:
* Backlog [2/22] * Backlog [3/23]
** TODO [#A] Add classmethod for metadata fetching to tracks :vrobbler:feature:music:personal:project: ** TODO [#A] Add classmethod for metadata fetching to tracks :vrobbler:feature:music:personal:project:
:PROPERTIES: :PROPERTIES:
:ID: bc4b45e5-4c65-13c5-ab7b-1937d3fbf5c2 :ID: bc4b45e5-4c65-13c5-ab7b-1937d3fbf5c2
@ -442,6 +442,32 @@ it's annoying.
** TODO [#C] Allow users to see tasks on calendar view :vrobbler:personal:project:templates:feature: ** TODO [#C] Allow users to see tasks on calendar view :vrobbler:personal:project:templates:feature:
https://codepen.io/oliviale/pen/QYqybo https://codepen.io/oliviale/pen/QYqybo
** TODO [#C] Come up with a possible flow using WebDAV and super-productivity for tasks :personal:feature:project:vrobbler:tasks: ** TODO [#C] Come up with a possible flow using WebDAV and super-productivity for tasks :personal:feature:project:vrobbler:tasks:
* Version 26.0 [3/3]
** DONE Clean up templates for scrobble details :vrobbler:personal:bug:templates:
:PROPERTIES:
:ID: 43dc1e02-c110-5b49-0ac7-4c4f7656d1aa
:END:
** DONE Add named locations visited to dashboard :vrobbler:personal:feature:locations:templates:
:PROPERTIES:
:ID: ebc365a1-cef4-f75d-569f-c24b072ef5a4
:END:
** DONE Add moods to dashboard :vrobbler:moods:feature:templates:personal:
:PROPERTIES:
:ID: c03a38ce-b337-f4fa-adba-aee08d4329f5
:END:
* Version 25.0 [3/3]
** DONE Add basic food templates and fix urls :food:vrobbler:personal:project:bug:urls:
:PROPERTIES:
:ID: 3de3459e-8e7e-abba-e068-b919a819d3e3
:END:
** DONE [#C] Fix how elapsed time is calculated :vrobbler:personal:project:scrobbles:bug:
:PROPERTIES:
:ID: cff58fc4-06ac-8016-4eae-130b51e3c9b7
:END:
** DONE Fix templates for videos and dashboard links :personal:feature:project:vrobbler:templates:
:PROPERTIES:
:ID: 7debfbaf-cdd8-f49b-57ff-804bfe7c9236
:END:
* Version 24.0 [2/2] * Version 24.0 [2/2]
** DONE Clean up logdata for various media :personal:feature:project:vrobbler:logdata: ** DONE Clean up logdata for various media :personal:feature:project:vrobbler:logdata:
:PROPERTIES: :PROPERTIES:

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.19 on 2025-09-11 13:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('foods', '0002_alter_food_run_time_seconds'),
]
operations = [
migrations.AddField(
model_name='food',
name='calories',
field=models.IntegerField(blank=True, null=True),
),
]

View File

@ -8,14 +8,15 @@ from django.urls import reverse
from django_extensions.db.models import TimeStampedModel from django_extensions.db.models import TimeStampedModel
from imagekit.models import ImageSpecField from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit from imagekit.processors import ResizeToFit
from scrobbles.dataclasses import BaseLogData from scrobbles.dataclasses import BaseLogData, WithPeopleLogData
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
BNULL = {"blank": True, "null": True} BNULL = {"blank": True, "null": True}
@dataclass @dataclass
class FoodLogData(BaseLogData): class FoodLogData(BaseLogData, WithPeopleLogData):
calories: Optional[int] = None
meal: Optional[str] = None meal: Optional[str] = None
rating: Optional[str] = None rating: Optional[str] = None
@ -48,6 +49,7 @@ class FoodCategory(TimeStampedModel):
class Food(ScrobblableMixin): class Food(ScrobblableMixin):
description = models.TextField(**BNULL) description = models.TextField(**BNULL)
calories = models.IntegerField(**BNULL)
allrecipe_image = models.ImageField(upload_to="food/recipe/", **BNULL) allrecipe_image = models.ImageField(upload_to="food/recipe/", **BNULL)
allrecipe_image_small = ImageSpecField( allrecipe_image_small = ImageSpecField(
source="allrecipe_image", source="allrecipe_image",
@ -72,7 +74,8 @@ class Food(ScrobblableMixin):
@property @property
def subtitle(self): def subtitle(self):
return self.category.name if self.category:
return self.category.name
@property @property
def strings(self) -> ScrobblableConstants: def strings(self) -> ScrobblableConstants:

View File

@ -1,5 +1,4 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional
from django.apps import apps 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

View File

@ -1,11 +1,13 @@
from decimal import Decimal, getcontext
import logging import logging
from dataclasses import dataclass
from decimal import Decimal
from typing import Dict from typing import Dict
from django.contrib.auth import get_user_model
from django.conf import settings from django.conf import settings
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 scrobbles.dataclasses import BaseLogData, WithPeopleLogData
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -15,6 +17,9 @@ User = get_user_model()
GEOLOC_ACCURACY = int(getattr(settings, "GEOLOC_ACCURACY", 4)) GEOLOC_ACCURACY = int(getattr(settings, "GEOLOC_ACCURACY", 4))
GEOLOC_PROXIMITY = Decimal(getattr(settings, "GEOLOC_PROXIMITY", "0.0001")) GEOLOC_PROXIMITY = Decimal(getattr(settings, "GEOLOC_PROXIMITY", "0.0001"))
@dataclass
class GeoLocationLogData(BaseLogData, WithPeopleLogData):
pass
class GeoLocation(ScrobblableMixin): class GeoLocation(ScrobblableMixin):
COMPLETION_PERCENT = getattr(settings, "LOCATION_COMPLETION_PERCENT", 100) COMPLETION_PERCENT = getattr(settings, "LOCATION_COMPLETION_PERCENT", 100)
@ -36,9 +41,13 @@ class GeoLocation(ScrobblableMixin):
def get_absolute_url(self): def get_absolute_url(self):
return reverse( return reverse(
"locations:geo_location_detail", kwargs={"slug": self.uuid} "locations:geolocation_detail", kwargs={"slug": self.uuid}
) )
@property
def logdata_cls(self):
return GeoLocationLogData
@classmethod @classmethod
def find_or_create(cls, data_dict: Dict) -> "GeoLocation": def find_or_create(cls, data_dict: Dict) -> "GeoLocation":
"""Given a data dict from GPSLogger, does the heavy lifting of looking up """Given a data dict from GPSLogger, does the heavy lifting of looking up

View File

@ -8,11 +8,11 @@ urlpatterns = [
path( path(
"locations/", "locations/",
views.GeoLocationListView.as_view(), views.GeoLocationListView.as_view(),
name="geo_locations_list", name="geolocation_list",
), ),
path( path(
"locations/<slug:slug>/", "locations/<slug:slug>/",
views.GeoLocationDetailView.as_view(), views.GeoLocationDetailView.as_view(),
name="geo_location_detail", name="geolocation_detail",
), ),
] ]

View File

@ -42,7 +42,7 @@ class Mood(ScrobblableMixin):
return str(self.uuid) return str(self.uuid)
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})
@property @property
def subtitle(self) -> str: def subtitle(self) -> str:

View File

@ -5,10 +5,10 @@ app_name = "moods"
urlpatterns = [ urlpatterns = [
path("moods/", views.MoodListView.as_view(), name="mood-list"), path("moods/", views.MoodListView.as_view(), name="mood_list"),
path( path(
"moods/<slug:slug>/", "moods/<slug:slug>/",
views.MoodDetailView.as_view(), views.MoodDetailView.as_view(),
name="mood-detail", name="mood_detail",
), ),
] ]

View File

@ -573,7 +573,7 @@ class Album(TimeStampedModel):
alt_name = name alt_name = name
album = Album.objects.filter( album = Album.objects.filter(
name=found_name, musicbrainz_id=album_dict.get("mbid") musicbrainz_id=album_dict.get("mbid")
).first() ).first()
if not album: if not album:

View File

@ -1,6 +1,7 @@
from django.views import generic from django.views import generic
from podcasts.models import Podcast from podcasts.models import Podcast
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView
class PodcastListView(generic.ListView): class PodcastListView(generic.ListView):
model = Podcast model = Podcast

View File

@ -655,6 +655,13 @@ class Scrobble(TimeStampedModel):
(scrobble.elapsed_time) (scrobble.elapsed_time)
) )
# Remove any locations without titles
if "GeoLocation" in scrobbles_by_type.keys():
for loc_scrobble in scrobbles_by_type["GeoLocation"]:
if not loc_scrobble.media_obj.title:
scrobbles_by_type["GeoLocation"].remove(loc_scrobble)
scrobbles_by_type["GeoLocation_count"] -= 1
return scrobbles_by_type return scrobbles_by_type
@classmethod @classmethod
@ -892,8 +899,9 @@ class Scrobble(TimeStampedModel):
if self.played_to_completion: if self.played_to_completion:
if self.playback_position_seconds: if self.playback_position_seconds:
return self.playback_position_seconds return self.playback_position_seconds
if self.media_obj.run_time_seconds: if self.media_obj and self.media_obj.run_time_seconds:
return self.media_obj.run_time_seconds return self.media_obj.run_time_seconds
return (timezone.now() - self.timestamp).seconds return (timezone.now() - self.timestamp).seconds
@property @property
@ -1070,6 +1078,8 @@ class Scrobble(TimeStampedModel):
media_obj = self.puzzle media_obj = self.puzzle
if self.task: if self.task:
media_obj = self.task media_obj = self.task
if self.food:
media_obj = self.food
return media_obj return media_obj
def __str__(self): def __str__(self):

View File

@ -47,8 +47,8 @@ class ScrobbleNtfyNotification(ScrobbleNotification):
self.click_url = self.url_tmpl.format(path=self.scrobble.finish_url) self.click_url = self.url_tmpl.format(path=self.scrobble.finish_url)
self.title = "Finish " + self.media_obj.strings.verb.lower() + "?" self.title = "Finish " + self.media_obj.strings.verb.lower() + "?"
if self.scrobble.log and isinstance(self.scrobble.log, dict) and self.scrobble.log.get("description"): if self.scrobble.log and isinstance(self.scrobble.log, dict) and self.scrobble.log.get("title"):
self.ntfy_str += f" - {self.scrobble.log.get('description')}" self.ntfy_str += f" - {self.scrobble.log.get('title')}"
def send(self): def send(self):
if ( if (
@ -72,7 +72,7 @@ class MoodNtfyNotification(BasicNtfyNotification):
def __init__(self, profile, **kwargs): def __init__(self, profile, **kwargs):
super().__init__(profile) super().__init__(profile)
self.ntfy_str: str = "Would you like to check in about your mood?" self.ntfy_str: str = "Would you like to check in about your mood?"
self.click_url = self.url_tmpl.format(path=reverse("moods:mood-list")) self.click_url = self.url_tmpl.format(path=reverse("moods:mood_list"))
self.title = "Mood Check-in!" self.title = "Mood Check-in!"
def send(self): def send(self):

View File

@ -368,7 +368,7 @@ def email_scrobble_board_game(
enriched_game = find_and_enrich_board_game_data(game) enriched_game = find_and_enrich_board_game_data(game)
if game.get("isBaseGame"): if game.get("isBaseGame"):
base_games[game.get("id")] = enriched_game base_games[game.get("id")] = enriched_game
elif game.get("isExpansion"): if game.get("isExpansion"):
expansions[game.get("id")] = enriched_game expansions[game.get("id")] = enriched_game
locations = {} locations = {}

View File

@ -2,6 +2,7 @@ import hashlib
import logging import logging
import re import re
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
@ -13,7 +14,10 @@ from django.utils import timezone
from profiles.models import UserProfile from profiles.models import UserProfile
from profiles.utils import now_user_timezone from profiles.utils import now_user_timezone
from scrobbles.constants import LONG_PLAY_MEDIA from scrobbles.constants import LONG_PLAY_MEDIA
from scrobbles.notifications import MoodNtfyNotification, ScrobbleNtfyNotification from scrobbles.notifications import (
MoodNtfyNotification,
ScrobbleNtfyNotification,
)
from scrobbles.tasks import ( from scrobbles.tasks import (
process_koreader_import, process_koreader_import,
process_lastfm_import, process_lastfm_import,
@ -21,6 +25,9 @@ from scrobbles.tasks import (
) )
from webdav.client import get_webdav_client from webdav.client import get_webdav_client
if TYPE_CHECKING:
from scrobbles.models import Scrobble
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
User = get_user_model() User = get_user_model()
@ -345,3 +352,22 @@ def extract_domain(url):
+ parsed_url.netloc.split(".")[-1] + parsed_url.netloc.split(".")[-1]
) )
return domain return domain
def fix_playback_position_seconds(*scrobbles: "Scrobble", commit=True) -> list["Scrobble"]:
updated_scrobbles = list()
for scrobble in scrobbles:
if not scrobble.media_obj:
logger.info(f"No media object found for scrobble {scrobble.id}, cannot update elapsed time")
continue
if scrobble.media_type == "Track" and scrobble.media_obj.run_time_seconds:
too_long = scrobble.playback_position_seconds > scrobble.media_obj.run_time_seconds
zero = scrobble.playback_position_seconds == 0
null = not scrobble.playback_position_seconds
if too_long or zero or null:
scrobble.playback_position_seconds = scrobble.media_obj.run_time_seconds
updated_scrobbles.append(scrobble)
if commit:
scrobble.save(update_fields=["playback_position_seconds"])
return updated_scrobbles

View File

@ -940,7 +940,7 @@ class ScrobbleDetailView(DetailView):
slug_url_kwarg = "uuid" slug_url_kwarg = "uuid"
def get_form_class(self): def get_form_class(self):
return self.object.media_obj.logdata_cls.form() return self.object.media_obj.logdata_cls().form()
def get_form(self): def get_form(self):
FormClass = self.get_form_class() FormClass = self.get_form_class()

View File

@ -5,7 +5,6 @@ app_name = "videos"
urlpatterns = [ urlpatterns = [
# path('', views.scrobble_endpoint, name='scrobble-list'),
path("movies/", views.MovieListView.as_view(), name="movie_list"), path("movies/", views.MovieListView.as_view(), name="movie_list"),
path("series/", views.SeriesListView.as_view(), name="series_list"), path("series/", views.SeriesListView.as_view(), name="series_list"),
path( path(
@ -13,6 +12,7 @@ urlpatterns = [
views.SeriesDetailView.as_view(), views.SeriesDetailView.as_view(),
name="series_detail", name="series_detail",
), ),
path('videos/', views.VideoListView.as_view(), name='video_list'),
path( path(
"video/<slug:slug>/", "video/<slug:slug>/",
views.VideoDetailView.as_view(), views.VideoDetailView.as_view(),

View File

@ -1,8 +1,10 @@
{% load urlreplace %} {% load urlreplace %}
{% load naturalduration %}
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
<tr> <tr>
<th scope="col">Latest</th>
<th scope="col">Title</th> <th scope="col">Title</th>
<th scope="col">Scrobbles</th> <th scope="col">Scrobbles</th>
<th scope="col">Start</th> <th scope="col">Start</th>
@ -11,6 +13,7 @@
<tbody> <tbody>
{% for obj in object_list %} {% for obj in object_list %}
<tr> <tr>
<td><a href="{{obj.scrobble_set.last.get_absolute_url}}">{{obj.scrobble_set.last.local_timestamp}}
<td><a href="{{obj.get_absolute_url}}">{{obj}}</a></td> <td><a href="{{obj.get_absolute_url}}">{{obj}}</a></td>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<td>{{obj.scrobble_count}}</td> <td>{{obj.scrobble_count}}</td>

View File

@ -219,7 +219,7 @@
{% if scrobble.media_obj.primary_image_url %}<div style="float:left;padding-right:10px;padding-bottom:10px;"><img src="{{scrobble.media_obj.primary_image_url}}" /></div>{% endif %} {% if scrobble.media_obj.primary_image_url %}<div style="float:left;padding-right:10px;padding-bottom:10px;"><img src="{{scrobble.media_obj.primary_image_url}}" /></div>{% endif %}
<p><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></p> <p><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></p>
{% if scrobble.media_obj.subtitle %}<p><em><a href="{{scrobble.media_obj.subtitle.get_absolute_url}}">{{scrobble.media_obj.subtitle}}</a></em></p>{% endif %} {% if scrobble.media_obj.subtitle %}<p><em><a href="{{scrobble.media_obj.subtitle.get_absolute_url}}">{{scrobble.media_obj.subtitle}}</a></em></p>{% endif %}
{% if scrobble.logdata %}{% if scrobble.logdata.description %}<p><em>{{scrobble.logdata.description}}</em></p>{% endif %}{% endif %} {% if scrobble.logdata %}{% if scrobble.logdata.title%}<p><em>{{scrobble.logdata.title}}</em></p>{% endif %}{% endif %}
<p><small>{{scrobble.local_timestamp|naturaltime}} from {{scrobble.source}}</small></p> <p><small>{{scrobble.local_timestamp|naturaltime}} from {{scrobble.source}}</small></p>
<div class="progress-bar" style="margin-right:5px;"> <div class="progress-bar" style="margin-right:5px;">
<span class="progress-bar-fill" style="width: {{scrobble.percent_played}}%;"></span> <span class="progress-bar-fill" style="width: {{scrobble.percent_played}}%;"></span>

View File

@ -55,7 +55,7 @@
<thead> <thead>
<tr> <tr>
<th scope="col">Date</th> <th scope="col">Date</th>
<th scope="col">Publisher</th> <th scope="col">Location</th>
<th scope="col">Players</th> <th scope="col">Players</th>
</tr> </tr>
</thead> </thead>
@ -63,7 +63,7 @@
{% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %} {% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td>{{scrobble.media_obj.publisher}}</td> <td>{{scrobble.logdata.location }}</td>
<td>{% if scrobble.logdata.player_log %}{{scrobble.logdata.player_log}}{% else %}No data{% endif %}</td> <td>{% if scrobble.logdata.player_log %}{{scrobble.logdata.player_log}}{% else %}No data{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -0,0 +1,37 @@
{% 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>
</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>
<th scope="col">Calories</th>
<th scope="col">Notes</th>
</tr>
</thead>
<tbody>
{% for scrobble in scrobbles.all %}
<tr>
<td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td>{% if scrobble.logdata.calories %}{{scrobble.logdata.calories}}{% else %}{{scrobble.media_obj.calories}}{% endif %}</td>
<td>{% for note in scrobble.logdata.notes %}{{note}}{% if not forloop.last %}; {% endif%}{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "base_list.html" %}
{% block title %}Foods{% 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">
{% include "_scrobblable_list.html" %}
</div>
</div>
</div>
{% endblock %}

View File

@ -57,12 +57,16 @@
<thead> <thead>
<tr> <tr>
<th scope="col">Date</th> <th scope="col">Date</th>
<th scope="col">With</th>
<th scope="col">Notes</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %} {% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td>{{scrobble.local_timestamp|naturaltime}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td>{% for person in scrobble.logdata.with_people%}{{person}}{% if not forloop.last %}, {% endif%}{% endfor %}</td>
<td>{% for note in scrobble.logdata.notes %}{{note}}{% if not forloop.last %}; {% endif%}{% endfor %}
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -67,13 +67,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for location in object_list %} {% include "_scrobblable_list.html" %}
<tr>
<td>{{location.scrobble_set.count}}</td>
<td>{{location.title}}</td>
<td><a href="{{location.get_absolute_url}}">{{location.lat}}x{{location.lon}}</a></td>
</tr>
{% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -28,7 +28,7 @@
<tbody> <tbody>
{% for scrobble in scrobbles.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -82,7 +82,7 @@
<tbody> <tbody>
{% for scrobble in object.scrobbles %} {% for scrobble in object.scrobbles %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td><a href="{{scrobble.track.get_absolute_url}}">{{scrobble.track.title}}</a></td> <td><a href="{{scrobble.track.get_absolute_url}}">{{scrobble.track.title}}</a></td>
<td><a href="{{scrobble.track.artist.get_absolute_url}}">{{scrobble.track.artist.name}}</a></td> <td><a href="{{scrobble.track.artist.get_absolute_url}}">{{scrobble.track.artist.name}}</a></td>
</tr> </tr>

View File

@ -83,7 +83,7 @@
<tbody> <tbody>
{% for scrobble in object.scrobbles %} {% for scrobble in object.scrobbles %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td><a href="{{scrobble.track.get_absolute_url}}">{{scrobble.track.title}}</a></td> <td><a href="{{scrobble.track.get_absolute_url}}">{{scrobble.track.title}}</a></td>
<td><a href="{{scrobble.track.primary_album.get_absolute_url}}">{{scrobble.track.primary_album.name}}</a></td> <td><a href="{{scrobble.track.primary_album.get_absolute_url}}">{{scrobble.track.primary_album.name}}</a></td>
</tr> </tr>

View File

@ -28,7 +28,7 @@
<tbody> <tbody>
{% for scrobble in scrobbles.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td><a href="{{scrobble.track.get_absolute_url}}">{{scrobble.track.title}}</a></td> <td><a href="{{scrobble.track.get_absolute_url}}">{{scrobble.track.title}}</a></td>
<td><a href="{{scrobble.track.primary_album.get_absolute_url}}">{{scrobble.track.primary_album}}</a></td> <td><a href="{{scrobble.track.primary_album.get_absolute_url}}">{{scrobble.track.primary_album}}</a></td>
<td><a href="{{scrobble.track.artist.get_absolute_url}}">{{scrobble.track.artist}}</a></td> <td><a href="{{scrobble.track.artist.get_absolute_url}}">{{scrobble.track.artist}}</a></td>

View File

@ -20,40 +20,7 @@
<hr /> <hr />
<div class="col-md"> <div class="col-md">
<div class="table-responsive"> <div class="table-responsive">{% include "_scrobblable_list.html" %}</div>
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Scrobbles</th>
<th scope="col">Track</th>
<th scope="col">Artist</th>
</tr>
</thead>
<tbody>
{% for track in object_list %}
<tr>
<td>{{track.scrobble_set.count}}</td>
<td><a href="{{track.get_absolute_url}}">{{track}}</a></td>
<td><a href="{{track.artist.get_absolute_url}}">{{track.artist}}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="pagination" style="margin-bottom:50px;">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -45,7 +45,7 @@
<tbody> <tbody>
{% for scrobble in scrobbles.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td>{{scrobble.podcast_episode}}</td> <td>{{scrobble.podcast_episode}}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -1,5 +1,6 @@
{% extends "base_list.html" %} {% extends "base_list.html" %}
{% block title %}Podcasts{% endblock %}
{% block lists %} {% block lists %}
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
@ -7,20 +8,19 @@
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
<tr> <tr>
<th scope="col">Series</th>
<th scope="col">Episode</th> <th scope="col">Episode</th>
<th scope="col">Podcast</th>
<th scope="col">Scrobbles</th> <th scope="col">Scrobbles</th>
<th scope="col">All time</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for obj in object_list %} {% for obj in object_list.all %}
{% for episode in obj.episode_set.all %} {{obj.episodes}}
{% for episode in obj.podcastepisode_set.all %}
<tr> <tr>
<td><a href="{{episode.get_absolute_url}}">{{episode}}</a></td> <td><a href="{{episode.get_absolute_url}}">{{episode}}</a></td>
<td><a href="{{obj.get_absolute_url}}">{{obj}}</a></td> <td><a href="{{obj.get_absolute_url}}">{{obj}}</a></td>
<td>{{episode.scrobble_set.count}}</td> <td>{{episode.scrobble_set.count}}</td>
<td></td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}

View File

@ -57,7 +57,7 @@
<tbody> <tbody>
{% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %} {% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -13,8 +13,27 @@
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %} {% else %}
No tracks today <p>No tracks today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'foods:food_list' %}">Food</a></h3>
{% if Food %}
{% with scrobbles=Food count=Food_count time=Food_time %}
{% include "scrobbles/_scrobble_table.html" %}
{% endwith %}
{% else %}
<p>No food today</p>
{% endif %}
<h3><a href="{% url 'moods:mood_list' %}">Moods</a></h3>
{% if Mood %}
{% with scrobbles=Mood count=Mood_count time=Mood_time %}
{% include "scrobbles/_scrobble_table.html" %}
{% endwith %}
{% else %}
<p>No moods felt today </p>
{% endif %}
</div> </div>
<div class="col-md"> <div class="col-md">
@ -27,7 +46,7 @@
<p>No tasks today</p> <p>No tasks today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'videos:movie_list' %}">Videos</a></h3> <h3><a href="{% url 'videos:video_list' %}">Videos</a></h3>
{% if Video %} {% if Video %}
{% with scrobbles=Video count=Video_count time=Video_time %} {% with scrobbles=Video count=Video_count time=Video_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
@ -81,11 +100,12 @@
<p>No board games today</p> <p>No board games today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'beers:beer_list' %}">Beers</a></h3>
{% if Beer %} {% if Beer %}
<h4>Beers</h4>
{% with scrobbles=Beer count=Beer_count time=Beer_time %} {% with scrobbles=Beer count=Beer_count time=Beer_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
<p>No beer today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'bricksets:brickset_list' %}">Brick sets</a></h3> <h3><a href="{% url 'bricksets:brickset_list' %}">Brick sets</a></h3>
@ -97,7 +117,7 @@
<p>No brick sets today</p> <p>No brick sets today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'puzzles:puzzle_list' %}">Puzzles</aa></h3> <h3><a href="{% url 'puzzles:puzzle_list' %}">Puzzles</a></h3>
{% if Puzzle %} {% if Puzzle %}
{% with scrobbles=Puzzle count=Puzzle_count time=Puzzle_time %} {% with scrobbles=Puzzle count=Puzzle_count time=Puzzle_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
@ -112,7 +132,16 @@
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %} {% else %}
<p>No books today</p> <p>No books read today</p>
{% endif %}
<h3><a href="{% url 'locations:geolocation_list' %}">Locations</a></h3>
{% if GeoLocation %}
{% with scrobbles=GeoLocation count=GeoLocation_count time=GeoLocation_time %}
{% include "scrobbles/_scrobble_table.html" %}
{% endwith %}
{% else %}
<p>No locations visited today</p>
{% endif %} {% endif %}
</div> </div>

View File

@ -1,10 +1,10 @@
{% load humanize %} {% load humanize %}
{% load naturalduration %} {% load naturalduration %}
<tr {% if scrobble.in_progress %}class="in-progress"{% endif %}> <tr {% if scrobble.in_progress %}class="in-progress"{% endif %}>
<td>{% if scrobble.in_progress %}{{scrobble.media_obj.strings.verb}} now | <a class="right" href="{% url "scrobbles:finish" scrobble.uuid %}">Finish</a>{% else %}{{scrobble.local_timestamp|naturaltime}}{% endif %}</td> <td>{% if scrobble.in_progress %}<a href="{{scrobble.get_absolute_url}}">{{scrobble.media_obj.strings.verb}} now</a> | <a class="right" href="{% url "scrobbles:finish" scrobble.uuid %}">Finish</a>{% else %}<a href="{{scrobble.get_absolute_url}}">{{scrobble.local_timestamp|naturaltime}}</a>{% endif %}</td>
<td> <td>
{% if scrobble.media_type == "Task" %} {% if scrobble.media_type == "Task" %}
<p><em><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title|truncatechars_html:45}} - {% if scrobble.logdata %}{% if scrobble.logdata.description %}{{scrobble.logdata.description}}{% endif %}{% endif %}</a></em></p> <p><em><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title|truncatechars_html:45}} - {% if scrobble.logdata %}{% if scrobble.logdata.title%}{{scrobble.logdata.title}}{% endif %}{% endif %}</a></em></p>
{% else %} {% else %}
<a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj|truncatechars_html:45}}</a> <a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj|truncatechars_html:45}}</a>
{% endif %} {% endif %}

View File

@ -12,7 +12,9 @@
<h1>{{ object.media_obj }} - {{object.media_type}}</h1> <h1>{{ object.media_obj }} - {{object.media_type}}</h1>
<!-- Your existing detail page content --> <!-- Your existing detail page content -->
{% if object.logdata.avg_seconds_per_page %}
<p>Rate: {{object.logdata.avg_seconds_per_page}}s per page</p> <p>Rate: {{object.logdata.avg_seconds_per_page}}s per page</p>
{% endif %}
<h2>Edit Log</h2> <h2>Edit Log</h2>
<form method="post" class="needs-validation" novalidate> <form method="post" class="needs-validation" novalidate>

View File

@ -20,7 +20,7 @@
<tbody> <tbody>
{% for scrobble in scrobbles.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td>{{scrobble.media_obj.round.season.name}}</td> <td>{{scrobble.media_obj.round.season.name}}</td>
<td>{{scrobble.media_obj.round.season.league}}</td> <td>{{scrobble.media_obj.round.season.league}}</td>
</tr> </tr>

View File

@ -57,7 +57,7 @@
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %} {% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -1,28 +1,32 @@
{% extends "base_list.html" %} {% extends "base_list.html" %}
{% block title %}Movies{% 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 %} {% block lists %}
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
<div class="table-responsive"> <div class="table-responsive">{% include "_scrobblable_list.html" %}</div>
<table class="table table-striped table-sm"> </div>
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Scrobbles</th>
<th scope="col">All time</th>
</tr>
</thead>
<tbody>
{% for obj in object_list %}
<tr>
<td><a href="{{obj.get_absolute_url}}">{{obj}}</a></td>
<td>{{obj.scrobble_set.count}}</td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -60,7 +60,7 @@
<tbody> <tbody>
{% for scrobble in scrobbles %} {% for scrobble in scrobbles %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td> <td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td>
<td>{{scrobble.media_obj.season_number}}</td> <td>{{scrobble.media_obj.season_number}}</td>
<td>{{scrobble.media_obj.episode_number}}</td> <td>{{scrobble.media_obj.episode_number}}</td>

View File

@ -1,32 +1,32 @@
{% extends "base_list.html" %} {% extends "base_list.html" %}
{% block title %}Series{% 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 %} {% block lists %}
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
<div class="table-responsive"> <div class="table-responsive">{% include "_scrobblable_list.html" %}</div>
<table class="table table-striped table-sm"> </div>
<thead>
<tr>
<th scope="col">Series</th>
<th scope="col">Episode</th>
<th scope="col">Scrobbles</th>
<th scope="col">All time</th>
</tr>
</thead>
<tbody>
{% for obj in object_list %}
{% for video in obj.video_set.all %}
<tr>
<td><a href="{{video.get_absolute_url}}">{{video}}</a></td>
<td><a href="{{obj.get_absolute_url}}">{{obj}}</a></td>
<td>{{video.scrobble_set.count}}</td>
<td></td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -87,7 +87,7 @@ dd {
<tbody> <tbody>
{% for scrobble in scrobbles.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -0,0 +1,32 @@
{% extends "base_list.html" %}
{% block title %}Videos{% 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">{% include "_scrobblable_list.html" %}</div>
</div>
</div>
{% endblock %}

View File

@ -48,12 +48,14 @@
<thead> <thead>
<tr> <tr>
<th scope="col">Date</th> <th scope="col">Date</th>
<th scope="col">Notes</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in scrobbles.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td>{% for note in scrobble.logdata.notes %}{{note}}{% if not forloop.last %}; {% endif%}{% endfor %}
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>