[bricksets] Add brick set scrobble type
This commit is contained in:
0
vrobbler/apps/bricksets/__init__.py
Normal file
0
vrobbler/apps/bricksets/__init__.py
Normal file
23
vrobbler/apps/bricksets/admin.py
Normal file
23
vrobbler/apps/bricksets/admin.py
Normal 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,
|
||||||
|
]
|
||||||
5
vrobbler/apps/bricksets/apps.py
Normal file
5
vrobbler/apps/bricksets/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BrickSetsConfig(AppConfig):
|
||||||
|
name = "bricksets"
|
||||||
106
vrobbler/apps/bricksets/migrations/0001_initial.py
Normal file
106
vrobbler/apps/bricksets/migrations/0001_initial.py
Normal 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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
vrobbler/apps/bricksets/migrations/__init__.py
Normal file
0
vrobbler/apps/bricksets/migrations/__init__.py
Normal file
63
vrobbler/apps/bricksets/models.py
Normal file
63
vrobbler/apps/bricksets/models.py
Normal 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 ""
|
||||||
14
vrobbler/apps/bricksets/urls.py
Normal file
14
vrobbler/apps/bricksets/urls.py
Normal 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",
|
||||||
|
),
|
||||||
|
]
|
||||||
10
vrobbler/apps/bricksets/views.py
Normal file
10
vrobbler/apps/bricksets/views.py
Normal 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
|
||||||
@ -6,6 +6,7 @@ JELLYFIN_AUDIO_ITEM_TYPES = ["Audio"]
|
|||||||
LONG_PLAY_MEDIA = {
|
LONG_PLAY_MEDIA = {
|
||||||
"videogames": "VideoGame",
|
"videogames": "VideoGame",
|
||||||
"books": "Book",
|
"books": "Book",
|
||||||
|
"bricksets": "BrickSet",
|
||||||
}
|
}
|
||||||
|
|
||||||
PLAY_AGAIN_MEDIA = {
|
PLAY_AGAIN_MEDIA = {
|
||||||
@ -13,6 +14,7 @@ PLAY_AGAIN_MEDIA = {
|
|||||||
"books": "Book",
|
"books": "Book",
|
||||||
"boardgames": "BoardGame",
|
"boardgames": "BoardGame",
|
||||||
"moods": "Mood",
|
"moods": "Mood",
|
||||||
|
"bricksets": "BrickSet",
|
||||||
}
|
}
|
||||||
|
|
||||||
MEDIA_END_PADDING_SECONDS = {
|
MEDIA_END_PADDING_SECONDS = {
|
||||||
|
|||||||
@ -30,6 +30,9 @@ class JSONDataclass(JSONWizard):
|
|||||||
def json(self):
|
def json(self):
|
||||||
return json.dumps(self.asdict)
|
return json.dumps(self.asdict)
|
||||||
|
|
||||||
|
class LongPlayLogData(JSONDataclass):
|
||||||
|
serial_scrobble_id: Optional[int]
|
||||||
|
long_play_complete: Optional[bool]
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BoardGameScoreLogData(JSONDataclass):
|
class BoardGameScoreLogData(JSONDataclass):
|
||||||
@ -69,7 +72,7 @@ class BoardGameScoreLogData(JSONDataclass):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BoardGameLogData(JSONDataclass):
|
class BoardGameLogData(LongPlayLogData):
|
||||||
players: Optional[list[BoardGameScoreLogData]] = None
|
players: Optional[list[BoardGameScoreLogData]] = None
|
||||||
location: Optional[str] = None
|
location: Optional[str] = None
|
||||||
geo_location_id: Optional[int] = None
|
geo_location_id: Optional[int] = None
|
||||||
@ -92,10 +95,13 @@ class BookPageLogData(JSONDataclass):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BookLogData(JSONDataclass):
|
class BookLogData(LongPlayLogData):
|
||||||
koreader_hash: Optional[str]
|
koreader_hash: Optional[str]
|
||||||
pages_read: Optional[int]
|
pages_read: Optional[int]
|
||||||
page_data: Optional[list[BookPageLogData]]
|
page_data: Optional[list[BookPageLogData]]
|
||||||
|
page_end: Optional[int]
|
||||||
|
page_start: Optional[int]
|
||||||
|
serial_scrobble_id: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -148,7 +154,13 @@ class VideoLogData(JSONDataclass):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VideoGameLogData(JSONDataclass):
|
class VideoGameLogData(LongPlayLogData):
|
||||||
emulated: bool = False
|
emulated: bool = False
|
||||||
console: Optional[str] = None
|
console: Optional[str] = None
|
||||||
emulator: Optional[str] = None
|
emulator: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BrickSetLogData(LongPlayLogData):
|
||||||
|
built_with_names: Optional[list[str]]
|
||||||
|
built_with_user_ids: Optional[list[str]]
|
||||||
|
|||||||
25
vrobbler/apps/scrobbles/migrations/0060_scrobble_brickset.py
Normal file
25
vrobbler/apps/scrobbles/migrations/0060_scrobble_brickset.py
Normal 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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -96,6 +96,11 @@ class ScrobblableMixin(TimeStampedModel):
|
|||||||
def find_or_create(cls) -> None:
|
def find_or_create(cls) -> None:
|
||||||
logger.warning("find_or_create() not implemented yet")
|
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 LongPlayScrobblableMixin(ScrobblableMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import pytz
|
|||||||
from boardgames.models import BoardGame
|
from boardgames.models import BoardGame
|
||||||
from books.koreader import process_koreader_sqlite_file
|
from books.koreader import process_koreader_sqlite_file
|
||||||
from books.models import Book
|
from books.models import Book
|
||||||
|
from bricksets.models import BrickSet
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@ -485,6 +486,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
WEBPAGE = "WebPage", "Web Page"
|
WEBPAGE = "WebPage", "Web Page"
|
||||||
LIFE_EVENT = "LifeEvent", "Life event"
|
LIFE_EVENT = "LifeEvent", "Life event"
|
||||||
MOOD = "Mood", "Mood"
|
MOOD = "Mood", "Mood"
|
||||||
|
BRICKSET = "BrickSet", "Brick set"
|
||||||
|
|
||||||
uuid = models.UUIDField(editable=False, **BNULL)
|
uuid = models.UUIDField(editable=False, **BNULL)
|
||||||
video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **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
|
LifeEvent, on_delete=models.DO_NOTHING, **BNULL
|
||||||
)
|
)
|
||||||
mood = models.ForeignKey(Mood, 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(
|
media_type = models.CharField(
|
||||||
max_length=14, choices=MediaType.choices, default=MediaType.VIDEO
|
max_length=14, choices=MediaType.choices, default=MediaType.VIDEO
|
||||||
)
|
)
|
||||||
@ -881,6 +884,8 @@ class Scrobble(TimeStampedModel):
|
|||||||
media_obj = self.life_event
|
media_obj = self.life_event
|
||||||
if self.mood:
|
if self.mood:
|
||||||
media_obj = self.mood
|
media_obj = self.mood
|
||||||
|
if self.brickset:
|
||||||
|
media_obj = self.brickset
|
||||||
return media_obj
|
return media_obj
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@ -107,6 +107,7 @@ INSTALLED_APPS = [
|
|||||||
"sports",
|
"sports",
|
||||||
"books",
|
"books",
|
||||||
"boardgames",
|
"boardgames",
|
||||||
|
"bricksets",
|
||||||
"videogames",
|
"videogames",
|
||||||
"locations",
|
"locations",
|
||||||
"webpages",
|
"webpages",
|
||||||
|
|||||||
@ -121,6 +121,7 @@ INSTALLED_APPS = [
|
|||||||
"sports",
|
"sports",
|
||||||
"books",
|
"books",
|
||||||
"boardgames",
|
"boardgames",
|
||||||
|
"bricksets",
|
||||||
"videogames",
|
"videogames",
|
||||||
"locations",
|
"locations",
|
||||||
"webpages",
|
"webpages",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import vrobbler.apps.scrobbles.views as scrobbles_views
|
|||||||
from vrobbler.apps.books.api.views import AuthorViewSet, BookViewSet
|
from vrobbler.apps.books.api.views import AuthorViewSet, BookViewSet
|
||||||
from vrobbler.apps.music import urls as music_urls
|
from vrobbler.apps.music import urls as music_urls
|
||||||
from vrobbler.apps.books import urls as book_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.sports import urls as sports_urls
|
||||||
from vrobbler.apps.podcasts import urls as podcast_urls
|
from vrobbler.apps.podcasts import urls as podcast_urls
|
||||||
from vrobbler.apps.videogames import urls as videogame_urls
|
from vrobbler.apps.videogames import urls as videogame_urls
|
||||||
@ -69,6 +70,7 @@ urlpatterns = [
|
|||||||
path("", include(video_urls, namespace="videos")),
|
path("", include(video_urls, namespace="videos")),
|
||||||
path("", include(videogame_urls, namespace="videogames")),
|
path("", include(videogame_urls, namespace="videogames")),
|
||||||
path("", include(boardgame_urls, namespace="boardgames")),
|
path("", include(boardgame_urls, namespace="boardgames")),
|
||||||
|
path("", include(bricksets_urls, namespace="bricksets")),
|
||||||
path("", include(sports_urls, namespace="sports")),
|
path("", include(sports_urls, namespace="sports")),
|
||||||
path("", include(locations_urls, namespace="locations")),
|
path("", include(locations_urls, namespace="locations")),
|
||||||
path("", include(webpages_urls, namespace="webpages")),
|
path("", include(webpages_urls, namespace="webpages")),
|
||||||
|
|||||||
Reference in New Issue
Block a user