Compare commits

...

27 Commits
23 ... 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
36cfdd6f6c [release] 24.0 2025-08-19 00:57:52 -04:00
b11d87af75 [logdata] Janky fix to people and platform ids 2025-08-19 00:46:08 -04:00
1cf50209a4 [bricksets] Add templates 2025-08-18 20:41:16 -04:00
8a5486fb2c [templates] Remove sidebar, add dashboard links 2025-08-18 20:28:43 -04:00
135d6e65fa [templates] Make vrobbler to go home page 2025-08-17 20:23:02 -04:00
965f2dd41b [tasks] Use title, not description 2025-08-17 20:21:08 -04:00
53 changed files with 480 additions and 262 deletions

View File

@ -442,6 +442,42 @@ 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]
** DONE Clean up logdata for various media :personal:feature:project:vrobbler:logdata:
:PROPERTIES:
:ID: d5cce807-1f45-ef19-45a4-9f7069fa2a93
:END:
** DONE Removed sidebar and add links to headers :personal:feature:templates:scrobbles:
:PROPERTIES:
:ID: 1a1c0aa6-0313-c8be-1676-5d6adddef0a4
:END:
* Version 23.0 [3/3] * Version 23.0 [3/3]
** DONE Add dynamic forms for LogData classes :personal:feature:vrobbler:project:forms:logdata: ** DONE Add dynamic forms for LogData classes :personal:feature:vrobbler:project:forms: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

@ -3,8 +3,6 @@ from typing import Union, get_args, get_origin
from django import forms from django import forms
from people.models import Person
class ExportScrobbleForm(forms.Form): class ExportScrobbleForm(forms.Form):
"""Provide options for downloading scrobbles""" """Provide options for downloading scrobbles"""
@ -76,10 +74,16 @@ def django_form_field_from_type(field_type, required=True):
def form_from_dataclass(dataclass): def form_from_dataclass(dataclass):
form_fields = {} form_fields = {}
# Override notes field override_fields = {}
for klass in dataclass.mro():
if hasattr(klass, "override_fields"):
print(klass, ": ", klass.override_fields())
override_fields.update(klass.override_fields())
print("overrides: ", override_fields)
for f in fields(dataclass): for f in fields(dataclass):
if f.name in dataclass.override_fields(): print(f)
form_fields[f.name] = dataclass.override_fields()[f.name] if f.name in override_fields:
form_fields[f.name] = override_fields[f.name]
continue continue
required = f.default is None and f.default_factory is None required = f.default is None and f.default_factory is None

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()
@ -969,11 +969,14 @@ class ScrobbleDetailView(DetailView):
original_value = (self.object.log or {}).get(field_name) original_value = (self.object.log or {}).get(field_name)
data[field_name] = original_value data[field_name] = original_value
if "with_people_ids" in data: if data.get("with_people_ids", False):
data["with_people_ids"] = [ data["with_people_ids"] = [
p.id for p in data["with_people_ids"] p.id for p in data["with_people_ids"]
] ]
if data.get("platform_id", False):
data["platform_id"] = data["platform_id"].id
self.object.log = data self.object.log = data
self.object.save(update_fields=["log"]) self.object.save(update_fields=["log"])
return redirect(self.object.get_absolute_url()) return redirect(self.object.get_absolute_url())

View File

@ -82,7 +82,7 @@ class Task(LongPlayScrobblableMixin):
def subtitle_for_user(self, user_id): def subtitle_for_user(self, user_id):
scrobble = self.scrobbles(user_id).first() scrobble = self.scrobbles(user_id).first()
return scrobble.logdata.description or "" return scrobble.logdata.title or ""
@classmethod @classmethod
def find_or_create(cls, title: str) -> "Task": def find_or_create(cls, title: str) -> "Task":

View File

@ -39,20 +39,13 @@ class VideoGameLogData(BaseLogData, LongPlayLogData, WithPeopleLogData):
@classmethod @classmethod
def override_fields(cls) -> dict: def override_fields(cls) -> dict:
fields = {} return {
for base in cls.mro()[1:]:
if hasattr(base, "override_fields"):
base_fields = base.override_fields()
fields.update(base_fields)
custom_fields = {
"platform_id": forms.ModelChoiceField( "platform_id": forms.ModelChoiceField(
queryset=VideoGamePlatform.objects.all(), queryset=VideoGamePlatform.objects.all(),
required=False, required=False,
widget=forms.Select(), widget=forms.Select(),
) )
} }
fields.update(custom_fields)
return fields
class VideoGamePlatform(TimeStampedModel): class VideoGamePlatform(TimeStampedModel):
@ -70,19 +63,6 @@ class VideoGamePlatform(TimeStampedModel):
) )
@dataclass
class VideoGameLogData(BaseLogData, LongPlayLogData, WithPeopleLogData):
platform_id: Optional[int] = None
emulated: Optional[bool] = False
emulator: Optional[str] = None
@property
def platform(self) -> VideoGamePlatform | None:
if not self.platform_id:
return
return VideoGamePlatform.objects.filter(id=self.platform_id).first()
class VideoGameCollection(TimeStampedModel): class VideoGameCollection(TimeStampedModel):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL) uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)

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

@ -67,10 +67,10 @@ class WebPage(ScrobblableMixin):
self.save(update_fields=["extract"]) self.save(update_fields=["extract"])
def get_absolute_url(self): def get_absolute_url(self):
return reverse("webpages:webpage-detail", kwargs={"slug": self.uuid}) return reverse("webpages:webpage_detail", kwargs={"slug": self.uuid})
def get_read_url(self): def get_read_url(self):
return reverse("webpages:webpage-read", kwargs={"slug": self.uuid}) return reverse("webpages:webpage_read", kwargs={"slug": self.uuid})
@property @property
def estimated_time_to_read_in_seconds(self): def estimated_time_to_read_in_seconds(self):

View File

@ -5,15 +5,15 @@ app_name = "webpages"
urlpatterns = [ urlpatterns = [
path("webpages/", views.WebPageListView.as_view(), name="webpage-list"), path("webpages/", views.WebPageListView.as_view(), name="webpage_list"),
path( path(
"webpages/<slug:slug>/", "webpages/<slug:slug>/",
views.WebPageDetailView.as_view(), views.WebPageDetailView.as_view(),
name="webpage-detail", name="webpage_detail",
), ),
path( path(
"webpage/<slug:slug>/read/", "webpage/<slug:slug>/read/",
views.WebPageReadView.as_view(), views.WebPageReadView.as_view(),
name="webpage-read", name="webpage_read",
), ),
] ]

View File

@ -32,7 +32,7 @@ class WebPageReadView(
if latest_scrobble.played_to_completion: if latest_scrobble.played_to_completion:
redirect( redirect(
reverse( reverse(
"webpages:webpage-detail", kwargs={"slug": webpage.uuid} "webpages:webpage_detail", kwargs={"slug": webpage.uuid}
) )
) )
return super().get(*args, **kwargs) return super().get(*args, **kwargs)

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

@ -175,7 +175,7 @@
</head> </head>
<body> <body>
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow"> <header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">Vrobbler</a> <a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="/">Vrobbler</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@ -208,85 +208,8 @@
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">
<span data-feather="music"></span>
Dashboard
</a>
</li>
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/charts/">
<span data-feather="bar-chart"></span>
Charts
</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/locations/">
<span data-feather="map"></span>
Locations
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/long-plays/">
<span data-feather="playv"></span>
Long plays
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/moods/">
<span data-feather="smiley"></span>
Moods
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/books/">
<span data-feather="book"></span>
Books
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/video-games/">
<span data-feather="video"></span>
Video games
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/board-games/">
<span data-feather="board"></span>
Board games
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/trails/">
<span data-feather="trail"></span>
Trails
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/beers/">
<span data-feather="beer"></span>
Beers
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/imports/">
<span data-feather="log"></span>
Imports
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/">
<span data-feather="key"></span>
Admin
</a>
</li>
{% endif %}
</ul>
{% block extra_nav %} {% block extra_nav %}
{% endblock %} {% endblock %}
<hr/>
{% if now_playing_list and user.is_authenticated %} {% if now_playing_list and user.is_authenticated %}
<ul> <ul>
@ -296,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

@ -45,7 +45,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>{% if scrobble.long_play_complete == True %}Yes{% endif %}</td> <td>{% if scrobble.logdata.long_play_complete == True %}Yes{% endif %}</td>
<td>{% if scrobble.in_progress %}Now reading{% else %}{{scrobble.session_pages_read}}{% endif %}</td> <td>{% if scrobble.in_progress %}Now reading{% else %}{{scrobble.session_pages_read}}{% endif %}</td>
<td>{% for author in scrobble.book.authors.all %}<a href="{{author.get_absolute_url}}">{{author}}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td> <td>{% for author in scrobble.book.authors.all %}<a href="{{author.get_absolute_url}}">{{author}}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
</tr> </tr>

View File

@ -0,0 +1,56 @@
{% extends "base_list.html" %}
{% load mathfilters %}
{% load static %}
{% load naturalduration %}
{% block title %}{{object.title}}{% endblock %}
{% block lists %}
<div class="row">
{% if object.cover%}
<p style="float:left; width:402px; padding:0; border: 1px solid #ccc">
<img src="{{object.cover.url}}" width=400 />
</p>
{% endif %}
<div style="float:left; width:600px; margin-left:10px; ">
{% if object.summary %}
<p>{{object.summary|safe|linebreaks|truncatewords:160}}</p>
<hr />
{% endif %}
<p style="float:right;">
<a href="{{object.openlibrary_link}}"><img src="{% static " images/openlibrary-logo.png" %}" width=35></a>
<a href="{{object.amazon_link}}"><img src="{% static " images/amazon-logo.png" %}" width=35></a>
</p>
</div>
</div>
<div class="row">
<p>{{scrobbles.count}} scrobbles</p>
</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">Completed</th>
<th scope="col">Time</th>
</tr>
</thead>
<tbody>
{% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr>
<td><a href="{{scrobble.get_absolute_url}}">{{scrobble.local_timestamp}}</a></td>
<td>{% if scrobble.logdata.long_play_complete == True %}Yes{% endif %}</td>
<td>{% if scrobble.in_progress %}Now reading{% else %}{{scrobble.session_pages_read}}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "base_list.html" %}
{% block title %}Brick Sets{% 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

@ -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

@ -7,90 +7,141 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
<h3><a href="{% url 'music:tracks_list' %}">Tracks</a></h3>
{% if Track %} {% if Track %}
<h2>Music</h2>
{% with scrobbles=Track count=Track_count time=Track_time %} {% with scrobbles=Track count=Track_count time=Track_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %}
<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">
<h3><a href="{% url 'tasks:task_list' %}">Tasks</a></h3>
{% if Task %} {% if Task %}
<h2>Latest tasks</h2>
{% with scrobbles=Task count=Task_count time=Task_time %} {% with scrobbles=Task count=Task_count time=Task_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %}
<p>No tasks today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'videos:video_list' %}">Videos</a></h3>
{% if Video %} {% if Video %}
<h2>Videos</h2>
{% 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" %}
{% endwith %} {% endwith %}
{% else %}
<p>No videos today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'webpages:webpage_list' %}">Web pages</a></h3>
{% if WebPage %} {% if WebPage %}
<h4>Web pages</h4>
{% with scrobbles=WebPage count=WebPage_count time=WebPage_time %} {% with scrobbles=WebPage count=WebPage_count time=WebPage_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else%}
<p>No web page read today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'sports:event_list' %}">Sport events</a></h3>
{% if SportEvent %} {% if SportEvent %}
<h2>Sports</h2>
{% with scrobbles=SportEvent count=SportEvent_count time=SportEvent_time %} {% with scrobbles=SportEvent count=SportEvent_count time=SportEvent_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %}
<p>No sports today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'podcasts:podcast_list' %}">Podcasts</a></h3>
{% if PodcastEpisode %} {% if PodcastEpisode %}
<h2>Latest podcasts</h2>
{% with scrobbles=PodcastEpisode count=PodcastEpisode_count time=PodcastEpisode_time %} {% with scrobbles=PodcastEpisode count=PodcastEpisode_count time=PodcastEpisode_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %}
<p>No podcasts today</p>
{% endif %} {% endif %}
<h3><a href="{% url "videogames:videogame_list" %}">Video games</a></h3>
{% if VideoGame %} {% if VideoGame %}
<h4>Video games</h4>
{% with scrobbles=VideoGame count=VideoGame_count time=VideoGame_time %} {% with scrobbles=VideoGame count=VideoGame_count time=VideoGame_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %}
<p>No video games today</p>
{% endif %} {% endif %}
<h3><a href="{% url "boardgames:boardgame_list" %}">Board games</a></h3>
{% if BoardGame %} {% if BoardGame %}
<h4>Board games</h4>
{% with scrobbles=BoardGame count=BoardGame_count time=BoardGame_time %} {% with scrobbles=BoardGame count=BoardGame_count time=BoardGame_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %}
<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>
{% if BrickSet %} {% if BrickSet %}
<h4>Brick sets</h4>
{% with scrobbles=BrickSet count=BrickSet_count time=BrickSet_time %} {% with scrobbles=BrickSet count=BrickSet_count time=BrickSet_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %}
<p>No brick sets today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'puzzles:puzzle_list' %}">Puzzles</a></h3>
{% if Puzzle %} {% if Puzzle %}
<h4>Puzzles</h4>
{% 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" %}
{% endwith %} {% endwith %}
{% else %}
<p>No puzzles today</p>
{% endif %} {% endif %}
<h3><a href="{% url 'books:book_list' %}">Books</a></h3>
{% if Book %} {% if Book %}
<h4>Books</h4>
{% with scrobbles=Book count=Book_count time=Book_time %} {% with scrobbles=Book count=Book_count time=Book_time %}
{% include "scrobbles/_scrobble_table.html" %} {% include "scrobbles/_scrobble_table.html" %}
{% endwith %} {% endwith %}
{% else %}
<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

@ -56,6 +56,14 @@
<h1 class="h2">{% if date %}{{date|naturaltime}}{% else %}{{title}}{% endif %}</h1> <h1 class="h2">{% if date %}{{date|naturaltime}}{% else %}{{title}}{% endif %}</h1>
<div class="btn-toolbar mb-2 mb-md-0"> <div class="btn-toolbar mb-2 mb-md-0">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<div class="btn-group me-2">
<form action="/admin/" method="get">
<button type="submit" class="btn btn-sm btn-outline-secondary">
<span data-feather="key"></span>
Admin
</button>
</form>
</div>
<div class="btn-group me-2"> <div class="btn-group me-2">
{% if user.profile.lastfm_username and not user.profile.lastfm_auto_import %} {% if user.profile.lastfm_username and not user.profile.lastfm_auto_import %}
<form action="{% url 'scrobbles:lastfm-import' %}" method="get"> <form action="{% url 'scrobbles:lastfm-import' %}" method="get">

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

@ -54,7 +54,7 @@
<thead> <thead>
<tr> <tr>
<th scope="col">Date</th> <th scope="col">Date</th>
<th scope="col">Description</th> <th scope="col">Title</th>
<th scope="col">Notes</th> <th scope="col">Notes</th>
<th scope="col">Source</th> <th scope="col">Source</th>
</tr> </tr>
@ -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><a href="{{scrobble.get_media_source_url}}">{{scrobble.logdata.description}}</a></td> <td><a href="{{scrobble.get_media_source_url}}">{{scrobble.logdata.title}}</a></td>
<td>{{scrobble.logdata.notes_as_str|safe}}</td> <td>{{scrobble.logdata.notes_as_str|safe}}</td>
<td>{{scrobble.source}}</td> <td>{{scrobble.source}}</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>