[bricksets] Add brick set scrobble type

This commit is contained in:
2024-09-07 02:02:16 -04:00
parent 14062f3b60
commit 7e961076b4
17 changed files with 312 additions and 3 deletions

View File

View File

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

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class BrickSetsConfig(AppConfig):
name = "bricksets"

View File

@ -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,
},
),
]

View File

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

View File

@ -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/<slug:slug>/",
views.BrickSetDetailView.as_view(),
name="brickset_detail",
),
]

View File

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

View File

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

View File

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

View File

@ -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",
),
),
]

View File

@ -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,
),
),
]

View File

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

View File

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

View File

@ -107,6 +107,7 @@ INSTALLED_APPS = [
"sports",
"books",
"boardgames",
"bricksets",
"videogames",
"locations",
"webpages",

View File

@ -121,6 +121,7 @@ INSTALLED_APPS = [
"sports",
"books",
"boardgames",
"bricksets",
"videogames",
"locations",
"webpages",

View File

@ -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")),