Compare commits

..

6 Commits
23 ... 24

15 changed files with 156 additions and 127 deletions

View File

@ -79,7 +79,7 @@ fetching and simple saving.
:LOGBOOK:
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
:END:
* Backlog [3/23]
* Backlog [2/22]
** TODO [#A] Add classmethod for metadata fetching to tracks :vrobbler:feature:music:personal:project:
:PROPERTIES:
:ID: bc4b45e5-4c65-13c5-ab7b-1937d3fbf5c2
@ -442,6 +442,16 @@ it's annoying.
** TODO [#C] Allow users to see tasks on calendar view :vrobbler:personal:project:templates:feature:
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:
* 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]
** DONE Add dynamic forms for LogData classes :personal:feature:vrobbler:project:forms:logdata:
:PROPERTIES:

View File

@ -3,8 +3,6 @@ from typing import Union, get_args, get_origin
from django import forms
from people.models import Person
class ExportScrobbleForm(forms.Form):
"""Provide options for downloading scrobbles"""
@ -76,10 +74,16 @@ def django_form_field_from_type(field_type, required=True):
def form_from_dataclass(dataclass):
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):
if f.name in dataclass.override_fields():
form_fields[f.name] = dataclass.override_fields()[f.name]
print(f)
if f.name in override_fields:
form_fields[f.name] = override_fields[f.name]
continue
required = f.default is None and f.default_factory is None

View File

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

View File

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

View File

@ -39,20 +39,13 @@ class VideoGameLogData(BaseLogData, LongPlayLogData, WithPeopleLogData):
@classmethod
def override_fields(cls) -> dict:
fields = {}
for base in cls.mro()[1:]:
if hasattr(base, "override_fields"):
base_fields = base.override_fields()
fields.update(base_fields)
custom_fields = {
return {
"platform_id": forms.ModelChoiceField(
queryset=VideoGamePlatform.objects.all(),
required=False,
widget=forms.Select(),
)
}
fields.update(custom_fields)
return fields
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):
name = models.CharField(max_length=255)
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)

View File

@ -67,10 +67,10 @@ class WebPage(ScrobblableMixin):
self.save(update_fields=["extract"])
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):
return reverse("webpages:webpage-read", kwargs={"slug": self.uuid})
return reverse("webpages:webpage_read", kwargs={"slug": self.uuid})
@property
def estimated_time_to_read_in_seconds(self):

View File

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

View File

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

View File

@ -175,7 +175,7 @@
</head>
<body>
<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">
<span class="navbar-toggler-icon"></span>
</button>
@ -208,85 +208,8 @@
{% endfor %}
</ul>
{% 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 %}
{% endblock %}
<hr/>
{% if now_playing_list and user.is_authenticated %}
<ul>

View File

@ -45,7 +45,7 @@
{% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr>
<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>{% for author in scrobble.book.authors.all %}<a href="{{author.get_absolute_url}}">{{author}}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
</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

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

View File

@ -56,6 +56,14 @@
<h1 class="h2">{% if date %}{{date|naturaltime}}{% else %}{{title}}{% endif %}</h1>
<div class="btn-toolbar mb-2 mb-md-0">
{% 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">
{% if user.profile.lastfm_username and not user.profile.lastfm_auto_import %}
<form action="{% url 'scrobbles:lastfm-import' %}" method="get">

View File

@ -54,7 +54,7 @@
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Description</th>
<th scope="col">Title</th>
<th scope="col">Notes</th>
<th scope="col">Source</th>
</tr>
@ -63,7 +63,7 @@
{% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr>
<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.source}}</td>
</tr>