From 7e961076b4126ab0b5b3fde667f8ed4908c6d0b2 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Sat, 7 Sep 2024 02:02:16 -0400 Subject: [PATCH] [bricksets] Add brick set scrobble type --- vrobbler/apps/bricksets/__init__.py | 0 vrobbler/apps/bricksets/admin.py | 23 ++++ vrobbler/apps/bricksets/apps.py | 5 + .../apps/bricksets/migrations/0001_initial.py | 106 ++++++++++++++++++ .../apps/bricksets/migrations/__init__.py | 0 vrobbler/apps/bricksets/models.py | 63 +++++++++++ vrobbler/apps/bricksets/urls.py | 14 +++ vrobbler/apps/bricksets/views.py | 10 ++ vrobbler/apps/scrobbles/constants.py | 2 + vrobbler/apps/scrobbles/dataclasses.py | 18 ++- .../migrations/0060_scrobble_brickset.py | 25 +++++ .../0061_alter_scrobble_media_type.py | 35 ++++++ vrobbler/apps/scrobbles/mixins.py | 5 + vrobbler/apps/scrobbles/models.py | 5 + vrobbler/settings-testing.py | 1 + vrobbler/settings.py | 1 + vrobbler/urls.py | 2 + 17 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 vrobbler/apps/bricksets/__init__.py create mode 100644 vrobbler/apps/bricksets/admin.py create mode 100644 vrobbler/apps/bricksets/apps.py create mode 100644 vrobbler/apps/bricksets/migrations/0001_initial.py create mode 100644 vrobbler/apps/bricksets/migrations/__init__.py create mode 100644 vrobbler/apps/bricksets/models.py create mode 100644 vrobbler/apps/bricksets/urls.py create mode 100644 vrobbler/apps/bricksets/views.py create mode 100644 vrobbler/apps/scrobbles/migrations/0060_scrobble_brickset.py create mode 100644 vrobbler/apps/scrobbles/migrations/0061_alter_scrobble_media_type.py diff --git a/vrobbler/apps/bricksets/__init__.py b/vrobbler/apps/bricksets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vrobbler/apps/bricksets/admin.py b/vrobbler/apps/bricksets/admin.py new file mode 100644 index 0000000..fc9d40e --- /dev/null +++ b/vrobbler/apps/bricksets/admin.py @@ -0,0 +1,23 @@ +from django.contrib import admin + +from bricksets.models import BrickSet +from scrobbles.admin import ScrobbleInline + + +class BrickSetInline(admin.TabularInline): + model = BrickSet + extra = 0 + + +@admin.register(BrickSet) +class BrickSetAdmin(admin.ModelAdmin): + date_hierarchy = "created" + list_display = ( + "uuid", + "title", + ) + ordering = ("-created",) + search_fields = ("title",) + inlines = [ + ScrobbleInline, + ] diff --git a/vrobbler/apps/bricksets/apps.py b/vrobbler/apps/bricksets/apps.py new file mode 100644 index 0000000..dccd308 --- /dev/null +++ b/vrobbler/apps/bricksets/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BrickSetsConfig(AppConfig): + name = "bricksets" diff --git a/vrobbler/apps/bricksets/migrations/0001_initial.py b/vrobbler/apps/bricksets/migrations/0001_initial.py new file mode 100644 index 0000000..f24c18a --- /dev/null +++ b/vrobbler/apps/bricksets/migrations/0001_initial.py @@ -0,0 +1,106 @@ +# Generated by Django 4.2.15 on 2024-09-07 05:38 + +from django.db import migrations, models +import django_extensions.db.fields +import taggit.managers +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("scrobbles", "0059_remove_scrobble_book_koreader_hash_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="BrickSet", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name="created" + ), + ), + ( + "modified", + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name="modified" + ), + ), + ( + "uuid", + models.UUIDField( + blank=True, + default=uuid.uuid4, + editable=False, + null=True, + ), + ), + ( + "title", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "run_time_seconds", + models.IntegerField(blank=True, null=True), + ), + ( + "run_time_ticks", + models.PositiveBigIntegerField(blank=True, null=True), + ), + ( + "number", + models.CharField(blank=True, max_length=10, null=True), + ), + ("release_year", models.IntegerField(blank=True, null=True)), + ("piece_count", models.IntegerField(blank=True, null=True)), + ( + "brickset_rating", + models.DecimalField( + blank=True, decimal_places=1, max_digits=3, null=True + ), + ), + ( + "lego_item_number", + models.CharField(blank=True, max_length=10, null=True), + ), + ( + "box_image", + models.ImageField( + blank=True, null=True, upload_to="brickset/boxes/" + ), + ), + ( + "set_image", + models.ImageField( + blank=True, null=True, upload_to="brickset/sets/" + ), + ), + ( + "genre", + taggit.managers.TaggableManager( + blank=True, + help_text="A comma-separated list of tags.", + through="scrobbles.ObjectWithGenres", + to="scrobbles.Genre", + verbose_name="Tags", + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/vrobbler/apps/bricksets/migrations/__init__.py b/vrobbler/apps/bricksets/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vrobbler/apps/bricksets/models.py b/vrobbler/apps/bricksets/models.py new file mode 100644 index 0000000..338b953 --- /dev/null +++ b/vrobbler/apps/bricksets/models.py @@ -0,0 +1,63 @@ +from django.apps import apps +from django.db import models +from django.urls import reverse +from django_extensions.db.models import TimeStampedModel +from imagekit.models import ImageSpecField +from imagekit.processors import ResizeToFit +from scrobbles.dataclasses import BrickSetLogData +from scrobbles.mixins import LongPlayScrobblableMixin + +BNULL = {"blank": True, "null": True} + + +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) + lego_item_number = models.CharField(max_length=10, **BNULL) + box_image = models.ImageField(upload_to="brickset/boxes/", **BNULL) + box_image_small = ImageSpecField( + source="box_image", + processors=[ResizeToFit(100, 100)], + format="JPEG", + options={"quality": 60}, + ) + box_image_medium = ImageSpecField( + source="box_image", + processors=[ResizeToFit(300, 300)], + format="JPEG", + options={"quality": 75}, + ) + set_image = models.ImageField(upload_to="brickset/sets/", **BNULL) + set_image_small = ImageSpecField( + source="set_image", + processors=[ResizeToFit(100, 100)], + format="JPEG", + options={"quality": 60}, + ) + set_image_medium = ImageSpecField( + source="set_image", + processors=[ResizeToFit(300, 300)], + format="JPEG", + options={"quality": 75}, + ) + + def get_absolute_url(self): + return reverse("bricksets:brickset_detail", kwargs={"slug": self.uuid}) + + @property + def logdata_cls(self): + return BrickSetLogData + + @classmethod + def find_or_create(cls, title: str) -> "BrickSet": + return cls.objects.filter(title=title).first() + + @property + def primary_image_url(self) -> str: + if self.box_image: + return self.box_image.url + return "" diff --git a/vrobbler/apps/bricksets/urls.py b/vrobbler/apps/bricksets/urls.py new file mode 100644 index 0000000..da29aad --- /dev/null +++ b/vrobbler/apps/bricksets/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +from bricksets import views + +app_name = "bricksets" + + +urlpatterns = [ + path("bricksets/", views.BrickSetListView.as_view(), name="brickset_list"), + path( + "bricksets//", + views.BrickSetDetailView.as_view(), + name="brickset_detail", + ), +] diff --git a/vrobbler/apps/bricksets/views.py b/vrobbler/apps/bricksets/views.py new file mode 100644 index 0000000..dc6ba98 --- /dev/null +++ b/vrobbler/apps/bricksets/views.py @@ -0,0 +1,10 @@ +from bricksets.models import BrickSet +from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView + + +class BrickSetListView(ScrobbleableListView): + model = BrickSet + + +class BrickSetDetailView(ScrobbleableDetailView): + model = BrickSet diff --git a/vrobbler/apps/scrobbles/constants.py b/vrobbler/apps/scrobbles/constants.py index 61377b2..9a63d24 100644 --- a/vrobbler/apps/scrobbles/constants.py +++ b/vrobbler/apps/scrobbles/constants.py @@ -6,6 +6,7 @@ JELLYFIN_AUDIO_ITEM_TYPES = ["Audio"] LONG_PLAY_MEDIA = { "videogames": "VideoGame", "books": "Book", + "bricksets": "BrickSet", } PLAY_AGAIN_MEDIA = { @@ -13,6 +14,7 @@ PLAY_AGAIN_MEDIA = { "books": "Book", "boardgames": "BoardGame", "moods": "Mood", + "bricksets": "BrickSet", } MEDIA_END_PADDING_SECONDS = { diff --git a/vrobbler/apps/scrobbles/dataclasses.py b/vrobbler/apps/scrobbles/dataclasses.py index 978482b..9f7c3dc 100644 --- a/vrobbler/apps/scrobbles/dataclasses.py +++ b/vrobbler/apps/scrobbles/dataclasses.py @@ -30,6 +30,9 @@ class JSONDataclass(JSONWizard): def json(self): return json.dumps(self.asdict) +class LongPlayLogData(JSONDataclass): + serial_scrobble_id: Optional[int] + long_play_complete: Optional[bool] @dataclass class BoardGameScoreLogData(JSONDataclass): @@ -69,7 +72,7 @@ class BoardGameScoreLogData(JSONDataclass): @dataclass -class BoardGameLogData(JSONDataclass): +class BoardGameLogData(LongPlayLogData): players: Optional[list[BoardGameScoreLogData]] = None location: Optional[str] = None geo_location_id: Optional[int] = None @@ -92,10 +95,13 @@ class BookPageLogData(JSONDataclass): @dataclass -class BookLogData(JSONDataclass): +class BookLogData(LongPlayLogData): koreader_hash: Optional[str] pages_read: Optional[int] page_data: Optional[list[BookPageLogData]] + page_end: Optional[int] + page_start: Optional[int] + serial_scrobble_id: Optional[int] @dataclass @@ -148,7 +154,13 @@ class VideoLogData(JSONDataclass): @dataclass -class VideoGameLogData(JSONDataclass): +class VideoGameLogData(LongPlayLogData): emulated: bool = False console: Optional[str] = None emulator: Optional[str] = None + + +@dataclass +class BrickSetLogData(LongPlayLogData): + built_with_names: Optional[list[str]] + built_with_user_ids: Optional[list[str]] diff --git a/vrobbler/apps/scrobbles/migrations/0060_scrobble_brickset.py b/vrobbler/apps/scrobbles/migrations/0060_scrobble_brickset.py new file mode 100644 index 0000000..aa2aa83 --- /dev/null +++ b/vrobbler/apps/scrobbles/migrations/0060_scrobble_brickset.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.15 on 2024-09-07 05:38 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("bricksets", "0001_initial"), + ("scrobbles", "0059_remove_scrobble_book_koreader_hash_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="scrobble", + name="brickset", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + to="bricksets.brickset", + ), + ), + ] diff --git a/vrobbler/apps/scrobbles/migrations/0061_alter_scrobble_media_type.py b/vrobbler/apps/scrobbles/migrations/0061_alter_scrobble_media_type.py new file mode 100644 index 0000000..d36fa2b --- /dev/null +++ b/vrobbler/apps/scrobbles/migrations/0061_alter_scrobble_media_type.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.15 on 2024-09-07 05:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("scrobbles", "0060_scrobble_brickset"), + ] + + operations = [ + migrations.AlterField( + model_name="scrobble", + name="media_type", + field=models.CharField( + choices=[ + ("Video", "Video"), + ("Track", "Track"), + ("PodcastEpisode", "Podcast episode"), + ("SportEvent", "Sport event"), + ("Book", "Book"), + ("VideoGame", "Video game"), + ("BoardGame", "Board game"), + ("GeoLocation", "GeoLocation"), + ("WebPage", "Web Page"), + ("LifeEvent", "Life event"), + ("Mood", "Mood"), + ("BrickSet", "Brick set"), + ], + default="Video", + max_length=14, + ), + ), + ] diff --git a/vrobbler/apps/scrobbles/mixins.py b/vrobbler/apps/scrobbles/mixins.py index 850a44a..6aa454c 100644 --- a/vrobbler/apps/scrobbles/mixins.py +++ b/vrobbler/apps/scrobbles/mixins.py @@ -96,6 +96,11 @@ class ScrobblableMixin(TimeStampedModel): def find_or_create(cls) -> None: logger.warning("find_or_create() not implemented yet") + def __str__(self): + if self.title: + return self.title + return str(self.uuid) + class LongPlayScrobblableMixin(ScrobblableMixin): class Meta: diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index 01e034a..35ce646 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -10,6 +10,7 @@ import pytz from boardgames.models import BoardGame from books.koreader import process_koreader_sqlite_file from books.models import Book +from bricksets.models import BrickSet from django.conf import settings from django.contrib.auth import get_user_model from django.db import models @@ -485,6 +486,7 @@ class Scrobble(TimeStampedModel): WEBPAGE = "WebPage", "Web Page" LIFE_EVENT = "LifeEvent", "Life event" MOOD = "Mood", "Mood" + BRICKSET = "BrickSet", "Brick set" uuid = models.UUIDField(editable=False, **BNULL) video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL) @@ -510,6 +512,7 @@ class Scrobble(TimeStampedModel): LifeEvent, on_delete=models.DO_NOTHING, **BNULL ) mood = models.ForeignKey(Mood, on_delete=models.DO_NOTHING, **BNULL) + brickset = models.ForeignKey(BrickSet, on_delete=models.DO_NOTHING, **BNULL) media_type = models.CharField( max_length=14, choices=MediaType.choices, default=MediaType.VIDEO ) @@ -881,6 +884,8 @@ class Scrobble(TimeStampedModel): media_obj = self.life_event if self.mood: media_obj = self.mood + if self.brickset: + media_obj = self.brickset return media_obj def __str__(self): diff --git a/vrobbler/settings-testing.py b/vrobbler/settings-testing.py index 505fd14..518a45d 100644 --- a/vrobbler/settings-testing.py +++ b/vrobbler/settings-testing.py @@ -107,6 +107,7 @@ INSTALLED_APPS = [ "sports", "books", "boardgames", + "bricksets", "videogames", "locations", "webpages", diff --git a/vrobbler/settings.py b/vrobbler/settings.py index 245157b..bcca566 100644 --- a/vrobbler/settings.py +++ b/vrobbler/settings.py @@ -121,6 +121,7 @@ INSTALLED_APPS = [ "sports", "books", "boardgames", + "bricksets", "videogames", "locations", "webpages", diff --git a/vrobbler/urls.py b/vrobbler/urls.py index d3b087d..dbb9c0f 100644 --- a/vrobbler/urls.py +++ b/vrobbler/urls.py @@ -6,6 +6,7 @@ import vrobbler.apps.scrobbles.views as scrobbles_views from vrobbler.apps.books.api.views import AuthorViewSet, BookViewSet from vrobbler.apps.music import urls as music_urls from vrobbler.apps.books import urls as book_urls +from vrobbler.apps.bricksets import urls as bricksets_urls from vrobbler.apps.sports import urls as sports_urls from vrobbler.apps.podcasts import urls as podcast_urls from vrobbler.apps.videogames import urls as videogame_urls @@ -69,6 +70,7 @@ urlpatterns = [ path("", include(video_urls, namespace="videos")), path("", include(videogame_urls, namespace="videogames")), path("", include(boardgame_urls, namespace="boardgames")), + path("", include(bricksets_urls, namespace="bricksets")), path("", include(sports_urls, namespace="sports")), path("", include(locations_urls, namespace="locations")), path("", include(webpages_urls, namespace="webpages")),