Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 36cfdd6f6c | |||
| b11d87af75 | |||
| 1cf50209a4 | |||
| 8a5486fb2c | |||
| 135d6e65fa | |||
| 965f2dd41b |
12
PROJECT.org
12
PROJECT.org
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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",
|
||||
),
|
||||
]
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
56
vrobbler/templates/bricksets/brickset_detail.html
Normal file
56
vrobbler/templates/bricksets/brickset_detail.html
Normal 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 %}
|
||||
23
vrobbler/templates/bricksets/brickset_list.html
Normal file
23
vrobbler/templates/bricksets/brickset_list.html
Normal 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 %}
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user