diff --git a/vrobbler/apps/bricksets/models.py b/vrobbler/apps/bricksets/models.py index 338b953..d1ded0b 100644 --- a/vrobbler/apps/bricksets/models.py +++ b/vrobbler/apps/bricksets/models.py @@ -16,7 +16,9 @@ class BrickSet(LongPlayScrobblableMixin): number = models.CharField(max_length=10, **BNULL) release_year = models.IntegerField(**BNULL) piece_count = models.IntegerField(**BNULL) - brickset_rating = models.DecimalField(max_digits=3, decimal_places=1, **BNULL) + brickset_rating = models.DecimalField( + max_digits=3, decimal_places=1, **BNULL + ) lego_item_number = models.CharField(max_length=10, **BNULL) box_image = models.ImageField(upload_to="brickset/boxes/", **BNULL) box_image_small = ImageSpecField( @@ -48,13 +50,23 @@ class BrickSet(LongPlayScrobblableMixin): def get_absolute_url(self): return reverse("bricksets:brickset_detail", kwargs={"slug": self.uuid}) + def __str__(self) -> str: + name = str(self.title) + if not self.title: + name = str(self.number) + return name + @property def logdata_cls(self): return BrickSetLogData @classmethod - def find_or_create(cls, title: str) -> "BrickSet": - return cls.objects.filter(title=title).first() + def find_or_create(cls, brickset_id: str) -> "BrickSet": + brickset = cls.objects.filter(number=brickset_id).first() + if not brickset: + # TODO: enrich this from the website + brickset = cls.objects.create(number=brickset_id) + return brickset @property def primary_image_url(self) -> str: diff --git a/vrobbler/apps/music/admin.py b/vrobbler/apps/music/admin.py index a3ebead..9174287 100644 --- a/vrobbler/apps/music/admin.py +++ b/vrobbler/apps/music/admin.py @@ -16,6 +16,7 @@ class AlbumAdmin(admin.ModelAdmin): "theaudiodb_mood", "musicbrainz_id", ) + raw_id_fields = ("album_artist",) list_filter = ( "theaudiodb_score", "theaudiodb_genre", @@ -53,6 +54,10 @@ class TrackAdmin(admin.ModelAdmin): "artist", "musicbrainz_id", ) + raw_id_fields = ( + "album", + "artist", + ) list_filter = ("album", "artist") search_fields = ("title",) ordering = ("-created",) diff --git a/vrobbler/apps/scrobbles/admin.py b/vrobbler/apps/scrobbles/admin.py index 57bb459..75a4197 100644 --- a/vrobbler/apps/scrobbles/admin.py +++ b/vrobbler/apps/scrobbles/admin.py @@ -27,7 +27,7 @@ class ScrobbleInline(admin.TabularInline): "geo_location", "task", "mood", - "brickset", + "brick_set", "trail", "beer", "web_page", @@ -122,9 +122,12 @@ class ScrobbleAdmin(admin.ModelAdmin): "video_game", "board_game", "geo_location", + "puzzle", + "paper", + "food", "task", "mood", - "brickset", + "brick_set", "trail", "beer", "web_page", diff --git a/vrobbler/apps/scrobbles/constants.py b/vrobbler/apps/scrobbles/constants.py index e978917..d176879 100644 --- a/vrobbler/apps/scrobbles/constants.py +++ b/vrobbler/apps/scrobbles/constants.py @@ -27,14 +27,14 @@ MEDIA_END_PADDING_SECONDS = { TODOIST_TASK_URL = "https://app.todoist.com/app/task/{id}" SCROBBLE_CONTENT_URLS = { - "-i": "https://www.imdb.com/title/", - "-s": "https://www.thesportsdb.com/event/", - "-g": "https://boardgamegeek.com/boardgame/", - "-u": "https://untappd.com/", - "-b": "https://www.amazon.com/", - "-t": "https://app.todoist.com/app/task/{id}", - "-i": "https://www.youtube.com/watch?v=", - "-p": "https://www.ipdb.plus/IPDb/puzzle.php?id=", + "-i": ["https://www.imdb.com/title/", "https://www.youtube.com/watch?v="], + "-s": ["https://www.thesportsdb.com/event/"], + "-g": ["https://boardgamegeek.com/boardgame/"], + "-u": ["https://untappd.com/"], + "-b": ["https://www.amazon.com/"], + "-t": ["https://app.todoist.com/app/task/{id}"], + "-p": ["https://www.ipdb.plus/IPDb/puzzle.php?id="], + "-l": ["https://brickset.com/sets/"], } EXCLUDE_FROM_NOW_PLAYING = ("GeoLocation",) @@ -49,6 +49,7 @@ MANUAL_SCROBBLE_FNS = { "-w": "manual_scrobble_webpage", "-t": "manual_scrobble_task", "-p": "manual_scrobble_puzzle", + "-l": "manual_scrobble_brickset", } diff --git a/vrobbler/apps/scrobbles/migrations/0070_rename_brickset_scrobble_brick_set.py b/vrobbler/apps/scrobbles/migrations/0070_rename_brickset_scrobble_brick_set.py new file mode 100644 index 0000000..7585d34 --- /dev/null +++ b/vrobbler/apps/scrobbles/migrations/0070_rename_brickset_scrobble_brick_set.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.19 on 2025-06-09 14:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("scrobbles", "0069_scrobble_puzzle_alter_scrobble_media_type"), + ] + + operations = [ + migrations.RenameField( + model_name="scrobble", + old_name="brickset", + new_name="brick_set", + ), + ] diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index 8a49a90..6b6d936 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -549,7 +549,7 @@ class Scrobble(TimeStampedModel): LifeEvent, on_delete=models.DO_NOTHING, **BNULL ) mood = models.ForeignKey(Mood, on_delete=models.DO_NOTHING, **BNULL) - brickset = models.ForeignKey( + brick_set = models.ForeignKey( BrickSet, on_delete=models.DO_NOTHING, **BNULL ) media_type = models.CharField( @@ -635,6 +635,14 @@ class Scrobble(TimeStampedModel): for scrobble in scrobble_qs: scrobbles_by_type[scrobble.media_type].append(scrobble) + if not scrobbles_by_type.get(scrobble.media_type + "_count"): + scrobbles_by_type[scrobble.media_type + "_count"] = 0 + scrobbles_by_type[scrobble.media_type + "_count"] += 1 + if not scrobbles_by_type.get(scrobble.media_type + "_time"): + scrobbles_by_type[scrobble.media_type + "_time"] = 0 + scrobbles_by_type[scrobble.media_type + "_time"] += int( + (scrobble.elapsed_time) + ) return scrobbles_by_type @@ -1011,8 +1019,8 @@ class Scrobble(TimeStampedModel): media_obj = self.life_event if self.mood: media_obj = self.mood - if self.brickset: - media_obj = self.brickset + if self.brick_set: + media_obj = self.brick_set if self.trail: media_obj = self.trail if self.beer: diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index bc079c7..08ed785 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -8,6 +8,7 @@ import pytz from beers.models import Beer from boardgames.models import BoardGame from books.models import Book +from bricksets.models import BrickSet from dateutil.parser import parse from django.utils import timezone from locations.constants import LOCATION_PROVIDERS @@ -23,7 +24,7 @@ from scrobbles.constants import ( SCROBBLE_CONTENT_URLS, ) from scrobbles.models import Scrobble -from scrobbles.utils import convert_to_seconds +from scrobbles.utils import convert_to_seconds, extract_domain from sports.models import SportEvent from sports.thesportsdb import lookup_event_from_thesportsdb from tasks.models import Task @@ -322,14 +323,12 @@ def manual_scrobble_from_url( we know about the content type, and routes it to the appropriate media scrobbler. Otherwise, return nothing.""" content_key = "" - try: - domain = url.split("//")[-1].split("/")[0] - except IndexError: - domain = None + domain = extract_domain(url) - for key, content_url in SCROBBLE_CONTENT_URLS.items(): - if domain in content_url: - content_key = key + for key, content_urls in SCROBBLE_CONTENT_URLS.items(): + for content_url in content_urls: + if domain in content_url: + content_key = key item_id = None if not content_key: @@ -343,8 +342,10 @@ def manual_scrobble_from_url( except IndexError: pass - if content_key == "-i": + if content_key == "-i" and "v=" in url: item_id = url.split("v=")[1].split("&")[0] + elif content_key == "-i" and "title/tt" in url: + item_id = "tt" + str(item_id) scrobble_fn = MANUAL_SCROBBLE_FNS[content_key] return eval(scrobble_fn)(item_id, user_id, action=action) @@ -500,6 +501,151 @@ def todoist_scrobble_task( return scrobble +def emacs_scrobble_update_task( + emacs_id: str, emacs_notes: dict, user_id: int +) -> Optional[Scrobble]: + scrobble = Scrobble.objects.filter( + in_progress=True, + user_id=user_id, + log__source_id=emacs_id, + log__source="emacs", + ).first() + + if not scrobble: + logger.info( + "[emacs_scrobble_update_task] no task found", + extra={ + "emacs_notes": emacs_notes, + "user_id": user_id, + "media_type": Scrobble.MediaType.TASK, + }, + ) + return + + notes_updated = False + for note in emacs_notes: + existing_note_ts = [ + n.get("timestamp") for n in scrobble.log.get("notes", []) + ] + if not scrobble.log.get('notes"'): + scrobble.log["notes"] = [] + if note.get("timestamp") not in existing_note_ts: + scrobble.log["notes"].append( + {note.get("timestamp"): note.get("content")} + ) + notes_updated = True + + if notes_updated: + scrobble.save(update_fields=["log"]) + + logger.info( + "[emacs_scrobble_update_task] emacs note added", + extra={ + "emacs_note": emacs_notes, + "user_id": user_id, + "media_type": Scrobble.MediaType.TASK, + }, + ) + + return scrobble + + +def emacs_scrobble_task( + task_data: dict, + user_id: int, + started: bool = False, + stopped: bool = False, +) -> Scrobble | None: + + prefix = "" + suffix = "" + + source_id = task_data.get("source_id") + for label in task_data.get("labels"): + if label in TODOIST_TITLE_PREFIX_LABELS: + prefix = label + if label in TODOIST_TITLE_SUFFIX_LABELS: + suffix = label + + if not prefix and not suffix: + logger.warning( + "Missing a prefix and suffix tag for task", + extra={"emacs_scrobble_task": task_data}, + ) + + title = " ".join([prefix.capitalize(), suffix.capitalize()]) + task = Task.find_or_create(title) + + timestamp = pendulum.parse(task_data.get("updated_at", timezone.now())) + in_progress_scrobble = Scrobble.objects.filter( + user_id=user_id, + in_progress=True, + log__source_id=source_id, + log__source="orgmode", + task=task, + ).last() + + if not in_progress_scrobble and stopped: + logger.info( + "[emacs_scrobble_task] cannot stop already stopped task", + extra={ + "emacs_id": source_id, + }, + ) + return + + if in_progress_scrobble and started: + logger.info( + "[emacs_scrobble_task] cannot start already started task", + extra={ + "emacs_id": source_id, + }, + ) + return in_progress_scrobble + + # Finish an in-progress scrobble + if in_progress_scrobble and stopped: + logger.info( + "[emacs_scrobble_task] finishing", + extra={ + "emacs_id": source_id, + }, + ) + in_progress_scrobble.stop(timestamp=timestamp, force_finish=True) + return in_progress_scrobble + + if in_progress_scrobble: + return in_progress_scrobble + + notes = task_data.pop("notes") + if notes: + task_data["notes"] = [] + for note in notes: + task_data["notes"].append( + {note.get("timestamp"): note.get("content")} + ) + + scrobble_dict = { + "user_id": user_id, + "timestamp": timestamp, + "playback_position_seconds": 0, + "source": "Org-mode", + "log": task_data, + } + + logger.info( + "[emacs_scrobble_task] creating", + extra={ + "task_id": task.id, + "user_id": user_id, + "scrobble_dict": scrobble_dict, + "media_type": Scrobble.MediaType.TASK, + }, + ) + scrobble = Scrobble.create_or_update(task, user_id, scrobble_dict) + return scrobble + + def manual_scrobble_task(url: str, user_id: int, action: Optional[str] = None): source_id = re.findall(r"\d+", url)[0] @@ -708,3 +854,34 @@ def manual_scrobble_puzzle( # TODO Kick out a process to enrich the media here, and in every scrobble event return Scrobble.create_or_update(puzzle, user_id, scrobble_dict) + + +def manual_scrobble_brickset( + brickset_id: str, user_id: int, action: Optional[str] = None +): + brickset = BrickSet.find_or_create(brickset_id) + + if not brickset: + logger.error(f"No brickset found for Brickset ID {brickset_id}") + return + + scrobble_dict = { + "user_id": user_id, + "timestamp": timezone.now(), + "playback_position_seconds": 0, + "source": "Vrobbler", + "log": {"serial_scrobble_id": ""}, + } + logger.info( + "[vrobbler-scrobble] brickset scrobble request received", + extra={ + "brickset_id": brickset.id, + "user_id": user_id, + "scrobble_dict": scrobble_dict, + "media_type": Scrobble.MediaType.BRICKSET, + }, + ) + + # TODO Kick out a process to enrich the media here, and in every scrobble event + # TODO Need to check for past scrobbles and auto populate serial scrobble id if possible + return Scrobble.create_or_update(brickset, user_id, scrobble_dict) diff --git a/vrobbler/apps/scrobbles/urls.py b/vrobbler/apps/scrobbles/urls.py index d8c162e..742deff 100644 --- a/vrobbler/apps/scrobbles/urls.py +++ b/vrobbler/apps/scrobbles/urls.py @@ -1,6 +1,6 @@ from django.urls import path from scrobbles import views -from tasks.webhooks import todoist_webhook +from tasks.webhooks import todoist_webhook, emacs_webhook app_name = "scrobbles" @@ -36,11 +36,6 @@ urlpatterns = [ views.web_scrobbler_webhook, name="web-scrobbler-webhook", ), - path( - "webhook/emacs/", - views.emacs_webhook, - name="emacs-webhook", - ), path( "webhook/gps/", views.gps_webhook, @@ -57,6 +52,13 @@ urlpatterns = [ name="mopidy-webhook", ), path("webhook/todoist/", todoist_webhook, name="todoist-webhook"), + path("webhook/emacs/", emacs_webhook, name="emacs_webhook"), + path("export/", views.export, name="export"), + path( + "imports/", + views.ScrobbleImportListView.as_view(), + name="import-detail", + ), path("export/", views.export, name="export"), path( "imports/", diff --git a/vrobbler/apps/scrobbles/utils.py b/vrobbler/apps/scrobbles/utils.py index f86fdc4..bf17b36 100644 --- a/vrobbler/apps/scrobbles/utils.py +++ b/vrobbler/apps/scrobbles/utils.py @@ -3,6 +3,7 @@ import logging import re from datetime import datetime, timedelta, tzinfo from sqlite3 import IntegrityError +from urllib.parse import urlparse import pytz from django.apps import apps @@ -17,9 +18,10 @@ from scrobbles.tasks import ( process_lastfm_import, process_retroarch_import, ) -from vrobbler.apps.scrobbles.notifications import NtfyNotification from webdav.client import get_webdav_client +from vrobbler.apps.scrobbles.notifications import NtfyNotification + logger = logging.getLogger(__name__) User = get_user_model() @@ -67,7 +69,8 @@ def get_scrobbles_for_media(media_obj, user: User) -> models.QuerySet: return Scrobble.objects.filter(media_query, user=user) -def get_recently_played_board_games(user: User) -> dict: ... +def get_recently_played_board_games(user: User) -> dict: + ... def get_long_plays_in_progress(user: User) -> dict: @@ -187,8 +190,8 @@ def delete_zombie_scrobbles(dry_run=True): def import_from_webdav_for_all_users(restart=False): """Grab a list of all users with WebDAV enabled and kickoff imports for them""" - from scrobbles.models import KoReaderImport from books.koreader import fetch_file_from_webdav + from scrobbles.models import KoReaderImport # LastFmImport = apps.get_model("scrobbles", "LastFMImport") webdav_enabled_user_ids = UserProfile.objects.filter( @@ -271,6 +274,7 @@ def get_file_md5_hash(file_path: str) -> str: file_hash.update(chunk) return file_hash.hexdigest() + def deduplicate_tracks(commit=False) -> int: from music.models import Track @@ -293,7 +297,11 @@ def deduplicate_tracks(commit=False) -> int: try: other.delete() except IntegrityError as e: - print("could not delete ", other.id, f": IntegrityError {e}") + print( + "could not delete ", + other.id, + f": IntegrityError {e}", + ) return len(dups) @@ -318,3 +326,13 @@ def send_stop_notifications_for_in_progress_scrobbles() -> int: notifications_sent += 1 return notifications_sent + + +def extract_domain(url): + parsed_url = urlparse(url) + domain = ( + parsed_url.netloc.split(".")[-2] + + "." + + parsed_url.netloc.split(".")[-1] + ) + return domain diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 48d16c5..40d519d 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -524,35 +524,6 @@ def gps_webhook(request): return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) -@csrf_exempt -@api_view(["POST"]) -def emacs_webhook(request): - try: - data_dict = json.loads(request.data) - except TypeError: - data_dict = request.data - - logger.info( - "[emacs_webhook] called", - extra={ - "post_data": data_dict, - "user_id": 1, - }, - ) - - # TODO Fix this so we have to authenticate! - user_id = 1 - if request.user.id: - user_id = request.user.id - - # scrobble = gpslogger_scrobble_location(data_dict, user_id) - - # if not scrobble: - # return Response({}, status=status.HTTP_200_OK) - - return Response({"post_data": post_data}, status=status.HTTP_200_OK) - - @csrf_exempt @permission_classes([IsAuthenticated]) @api_view(["POST"]) diff --git a/vrobbler/apps/tasks/models.py b/vrobbler/apps/tasks/models.py index 0c30c78..8116207 100644 --- a/vrobbler/apps/tasks/models.py +++ b/vrobbler/apps/tasks/models.py @@ -50,7 +50,8 @@ class Task(LongPlayScrobblableMixin): url = "" scrobble = self.scrobbles(user_id).first() if scrobble: - url = TODOIST_TASK_URL.format(id=scrobble.logdata.todoist_id) + if scrobble.log.get("source") == "todoist": + url = TODOIST_TASK_URL.format(id=scrobble.logdata.todist_id) return url def subtitle_for_user(self, user_id): diff --git a/vrobbler/apps/tasks/webhooks.py b/vrobbler/apps/tasks/webhooks.py index 0d9c793..68225fc 100644 --- a/vrobbler/apps/tasks/webhooks.py +++ b/vrobbler/apps/tasks/webhooks.py @@ -1,16 +1,18 @@ +import json import logging -import pendulum from django.views.decorators.csrf import csrf_exempt +from profiles.models import UserProfile from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from scrobbles.scrobblers import ( + emacs_scrobble_task, + emacs_scrobble_update_task, todoist_scrobble_task, todoist_scrobble_update_task, ) -from profiles.models import UserProfile logger = logging.getLogger(__name__) @@ -29,7 +31,7 @@ def todoist_webhook(request): todoist_type, todoist_event = post_data.get("event_name").split(":") event_data = post_data.get("event_data", {}) is_item_type = todoist_type == "item" - is_note_type = todoist_type == "note" + is_note_type = todoist_tyllll = "note" new_labels = event_data.get("labels", []) old_labels = ( post_data.get("event_data_extra", {}) @@ -118,3 +120,54 @@ def todoist_webhook(request): extra={"scrobble_id": scrobble.id}, ) return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) + + +@csrf_exempt +@permission_classes([IsAuthenticated]) +@api_view(["POST"]) +def emacs_webhook(request): + try: + post_data = json.loads(request.data) + except TypeError: + post_data = request.data + logger.info( + "[emacs_webhook] called", + extra={"post_data": post_data}, + ) + task_in_progress = post_data.get("state") == "STRT" + task_stopped = post_data.get("state") == "DONE" + post_data["source_id"] = post_data.pop("emacs_id") + + # TODO: Figure out why token auth is not working + user_id = request.user.id + if not user_id: + user_id = 1 + + scrobble = None + if post_data.get("source_id"): + scrobble = emacs_scrobble_task( + post_data, user_id, started=task_in_progress, stopped=task_stopped + ) + + if scrobble and task_in_progress and post_data.get("notes"): + emacs_scrobble_update_task( + post_data.get("source_id"), + post_data.get("notes"), + user_id, + ) + + if not scrobble: + logger.info( + "[emacs_webhook] finished with no note or task found", + extra={"scrobble_id": None}, + ) + return Response( + {"error": "No scrobble found to be updated"}, + status=status.HTTP_304_NOT_MODIFIED, + ) + + logger.info( + "[emacs_webhook] finished", + extra={"scrobble_id": scrobble.id}, + ) + return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) diff --git a/vrobbler/apps/videos/metadata.py b/vrobbler/apps/videos/metadata.py index 3bdc02d..80c3ca0 100644 --- a/vrobbler/apps/videos/metadata.py +++ b/vrobbler/apps/videos/metadata.py @@ -57,6 +57,8 @@ class VideoMetadata: def as_dict_with_cover_and_genres(self) -> tuple: video_dict = vars(self) - cover = video_dict.pop("cover_url") + cover = None + if "cover_url" in video_dict.keys(): + cover = video_dict.pop("cover_url") genres = video_dict.pop("genres") return video_dict, cover, genres diff --git a/vrobbler/templates/scrobbles/_last_scrobbles.html b/vrobbler/templates/scrobbles/_last_scrobbles.html index fe50ae8..13fc631 100644 --- a/vrobbler/templates/scrobbles/_last_scrobbles.html +++ b/vrobbler/templates/scrobbles/_last_scrobbles.html @@ -9,7 +9,7 @@
{% if Track %}

Music

- {% with scrobbles=Track %} + {% with scrobbles=Track count=Track_count time=Track_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} @@ -18,63 +18,70 @@ {% if Task %}

Latest tasks

- {% with scrobbles=Task %} + {% with scrobbles=Task count=Task_count time=Task_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} {% if Video %}

Videos

- {% with scrobbles=Video %} + {% with scrobbles=Video count=Video_count time=Video_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} {% if WebPage %}

Web pages

- {% with scrobbles=WebPage %} + {% with scrobbles=WebPage count=WebPage_count time=WebPage_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} {% if SportEvent %}

Sports

- {% with scrobbles=SportEvent %} + {% with scrobbles=SportEvent count=SportEvent_count time=SportEvent_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} {% if PodcastEpisode %}

Latest podcasts

- {% with scrobbles=PodcastEpisode %} + {% with scrobbles=PodcastEpisode count=PodcastEpisode_count time=PodcastEpisode_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} {% if VideoGame %}

Video games

- {% with scrobbles=VideoGame %} + {% with scrobbles=VideoGame count=VideoGame_count time=VideoGame_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} {% if BoardGame %}

Board games

- {% with scrobbles=BoardGame %} + {% with scrobbles=BoardGame count=BoardGame_count time=BoardGame_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} {% if Beer %}

Beers

- {% with scrobbles=Beer %} + {% with scrobbles=Beer count=Beer_count time=Beer_time %} + {% include "scrobbles/_scrobble_table.html" %} + {% endwith %} + {% endif %} + + {% if BrickSet %} +

Brick sets

+ {% with scrobbles=BrickSet count=BrickSet_count time=BrickSet_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} {% if Book %}

Books

- {% with scrobbles=Book %} + {% with scrobbles=Book count=Book_count time=Book_time %} {% include "scrobbles/_scrobble_table.html" %} {% endwith %} {% endif %} diff --git a/vrobbler/templates/scrobbles/_row.html b/vrobbler/templates/scrobbles/_row.html index 54a1fda..8b2ee48 100644 --- a/vrobbler/templates/scrobbles/_row.html +++ b/vrobbler/templates/scrobbles/_row.html @@ -1,7 +1,7 @@ {% load humanize %} {% load naturalduration %} - + {% if scrobble.in_progress %}{{scrobble.media_obj.strings.verb}} now | Finish{% else %}{{scrobble.timestamp|naturaltime}}{% endif %} - {{scrobble.media_obj|truncatechars_html:50}} + {{scrobble.media_obj|truncatechars_html:45}} {{scrobble.elapsed_time|natural_duration}} diff --git a/vrobbler/templates/scrobbles/_scrobble_table.html b/vrobbler/templates/scrobbles/_scrobble_table.html index 42f3f3b..f9f1d0c 100644 --- a/vrobbler/templates/scrobbles/_scrobble_table.html +++ b/vrobbler/templates/scrobbles/_scrobble_table.html @@ -4,6 +4,7 @@
+ {{count}} scrobble {% if time %}| {{time|natural_duration}}{% endif %} diff --git a/vrobbler/templates/scrobbles/scrobble_list.html b/vrobbler/templates/scrobbles/scrobble_list.html index db3b8e5..66f56ef 100644 --- a/vrobbler/templates/scrobbles/scrobble_list.html +++ b/vrobbler/templates/scrobbles/scrobble_list.html @@ -43,6 +43,10 @@ background:rgba(0,0,0,0.4); } + .in-progress { + background: #CCFFFF; + } + .in-progress a { color: black } {% endblock %} {% block content %} diff --git a/vrobbler/templates/tasks/task_detail.html b/vrobbler/templates/tasks/task_detail.html index 1882a28..d8f4abf 100644 --- a/vrobbler/templates/tasks/task_detail.html +++ b/vrobbler/templates/tasks/task_detail.html @@ -62,6 +62,7 @@ + {% endfor %}
{{scrobble.timestamp}} {{scrobble.logdata.description}} {{scrobble.source}}{{scrobble.log.notes}}