[puzzles] Add puzzle model and hooks
This commit is contained in:
28
vrobbler/apps/puzzles/admin.py
Normal file
28
vrobbler/apps/puzzles/admin.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from puzzles.models import Puzzle, PuzzleManufacturer
|
||||||
|
from django.contrib import admin
|
||||||
|
from scrobbles.admin import ScrobbleInline
|
||||||
|
|
||||||
|
|
||||||
|
class PuzzleInline(admin.TabularInline):
|
||||||
|
model = Puzzle
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(PuzzleManufacturer)
|
||||||
|
class PuzzleManufacturerAdmin(admin.ModelAdmin):
|
||||||
|
date_hierarchy = "created"
|
||||||
|
search_fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Puzzle)
|
||||||
|
class PuzzleAdmin(admin.ModelAdmin):
|
||||||
|
date_hierarchy = "created"
|
||||||
|
list_display = (
|
||||||
|
"uuid",
|
||||||
|
"title",
|
||||||
|
)
|
||||||
|
ordering = ("-created",)
|
||||||
|
search_fields = ("title",)
|
||||||
|
inlines = [
|
||||||
|
ScrobbleInline,
|
||||||
|
]
|
||||||
5
vrobbler/apps/puzzles/apps.py
Normal file
5
vrobbler/apps/puzzles/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PuzzlesConfig(AppConfig):
|
||||||
|
name = "puzzles"
|
||||||
163
vrobbler/apps/puzzles/migrations/0001_initial.py
Normal file
163
vrobbler/apps/puzzles/migrations/0001_initial.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# Generated by Django 4.2.19 on 2025-05-11 03:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django_extensions.db.fields
|
||||||
|
import taggit.managers
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("scrobbles", "0068_scrobble_paper_alter_scrobble_media_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PuzzleManufacturer",
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=255)),
|
||||||
|
(
|
||||||
|
"ipdb_id",
|
||||||
|
models.CharField(blank=True, max_length=200, null=True),
|
||||||
|
),
|
||||||
|
("description", models.TextField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"get_latest_by": "modified",
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Puzzle",
|
||||||
|
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(default=900)),
|
||||||
|
(
|
||||||
|
"run_time_ticks",
|
||||||
|
models.PositiveBigIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
("description", models.TextField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"orientation",
|
||||||
|
models.CharField(blank=True, max_length=50, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dimensions_in_inches",
|
||||||
|
models.CharField(blank=True, max_length=10, null=True),
|
||||||
|
),
|
||||||
|
("publish_year", models.IntegerField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"material",
|
||||||
|
models.CharField(blank=True, max_length=50, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"cut_style",
|
||||||
|
models.CharField(blank=True, max_length=100, null=True),
|
||||||
|
),
|
||||||
|
("pieces_count", models.IntegerField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"igdb_id",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"barcode",
|
||||||
|
models.CharField(blank=True, max_length=13, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"igdb_image",
|
||||||
|
models.ImageField(
|
||||||
|
blank=True, null=True, upload_to="puzzles/igdb/"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"genre",
|
||||||
|
taggit.managers.TaggableManager(
|
||||||
|
blank=True,
|
||||||
|
help_text="A comma-separated list of tags.",
|
||||||
|
through="scrobbles.ObjectWithGenres",
|
||||||
|
to="scrobbles.Genre",
|
||||||
|
verbose_name="Tags",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"manufacturer",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||||
|
to="puzzles.puzzlemanufacturer",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.19 on 2025-05-11 03:24
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("puzzles", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="puzzle",
|
||||||
|
name="dimensions_in_inches",
|
||||||
|
field=models.CharField(blank=True, max_length=30, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
vrobbler/apps/puzzles/migrations/__init__.py
Normal file
0
vrobbler/apps/puzzles/migrations/__init__.py
Normal file
108
vrobbler/apps/puzzles/models.py
Normal file
108
vrobbler/apps/puzzles/models.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from puzzles.sources import ipdb
|
||||||
|
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 PuzzleLogData
|
||||||
|
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
|
||||||
|
|
||||||
|
BNULL = {"blank": True, "null": True}
|
||||||
|
|
||||||
|
|
||||||
|
class PuzzleManufacturer(TimeStampedModel):
|
||||||
|
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
ipdb_id = models.CharField(max_length=200, **BNULL)
|
||||||
|
description = models.TextField(**BNULL)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class Puzzle(ScrobblableMixin):
|
||||||
|
description = models.TextField(**BNULL)
|
||||||
|
orientation = models.CharField(max_length=50, **BNULL)
|
||||||
|
dimensions_in_inches = models.CharField(max_length=30, **BNULL)
|
||||||
|
publish_year = models.IntegerField(**BNULL)
|
||||||
|
material = models.CharField(max_length=50, **BNULL)
|
||||||
|
cut_style = models.CharField(max_length=100, **BNULL)
|
||||||
|
pieces_count = models.IntegerField(**BNULL)
|
||||||
|
igdb_id = models.CharField(max_length=255, **BNULL)
|
||||||
|
barcode = models.CharField(max_length=13, **BNULL)
|
||||||
|
igdb_image = models.ImageField(upload_to="puzzles/igdb/", **BNULL)
|
||||||
|
igdb_image_small = ImageSpecField(
|
||||||
|
source="igdb_image",
|
||||||
|
processors=[ResizeToFit(100, 100)],
|
||||||
|
format="JPEG",
|
||||||
|
options={"quality": 60},
|
||||||
|
)
|
||||||
|
igdb_image_medium = ImageSpecField(
|
||||||
|
source="igdb_image",
|
||||||
|
processors=[ResizeToFit(300, 300)],
|
||||||
|
format="JPEG",
|
||||||
|
options={"quality": 75},
|
||||||
|
)
|
||||||
|
manufacturer = models.ForeignKey(
|
||||||
|
PuzzleManufacturer, on_delete=models.DO_NOTHING, **BNULL
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_absolute_url(self) -> str:
|
||||||
|
return reverse("puzzles:puzzle_detail", kwargs={"slug": self.uuid})
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.title} ({self.pieces_count}) by {self.manufacturer}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subtitle(self):
|
||||||
|
return self.manufacturer.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def strings(self) -> ScrobblableConstants:
|
||||||
|
return ScrobblableConstants(verb="Solving", tags="puzzle")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def igdb_link(self) -> str:
|
||||||
|
link = ""
|
||||||
|
if self.igdb_id:
|
||||||
|
link = f"https://www.ipdb.plus/IPDb/puzzle.php?id={self.igdb_id}"
|
||||||
|
return link
|
||||||
|
|
||||||
|
@property
|
||||||
|
def primary_image_url(self) -> str:
|
||||||
|
url = ""
|
||||||
|
if self.ipdb_image:
|
||||||
|
url = self.ipdb_image.url
|
||||||
|
return url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logdata_cls(self):
|
||||||
|
return PuzzleLogData
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_or_create(cls, ipdb_id: str) -> "Puzzle":
|
||||||
|
puzzle = cls.objects.filter(ipdb_id=ipdb_id).first()
|
||||||
|
|
||||||
|
if not puzzle:
|
||||||
|
puzzle_dict = ipdb.get_puzzle_from_ipdb_id(ipdb_id)
|
||||||
|
manufacturer_name = puzzle_dict.pop("manufacturer")
|
||||||
|
manufacturer, _created = PuzzleManufacturer.objects.get_or_create(
|
||||||
|
name=manufacturer_name
|
||||||
|
)
|
||||||
|
ipdb_dict["manufacturer_id"] = manufacturer.id
|
||||||
|
|
||||||
|
genres = puzzle_dict.pop("genres")
|
||||||
|
puzzle = Puzzle.objects.create(**puzzle_dict)
|
||||||
|
if genres:
|
||||||
|
puzzle.genre.add(*genres)
|
||||||
|
|
||||||
|
return puzzle
|
||||||
|
|
||||||
|
def scrobbles(self, user_id):
|
||||||
|
Scrobble = apps.get_model("scrobbles", "Scrobble")
|
||||||
|
return Scrobble.objects.filter(user_id=user_id, puzzle=self).order_by(
|
||||||
|
"-timestamp"
|
||||||
|
)
|
||||||
5
vrobbler/apps/puzzles/sources/ipdb.py
Normal file
5
vrobbler/apps/puzzles/sources/ipdb.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
|
||||||
|
def get_puzzle_from_ipdb_id(ipdb_id: str) -> dict:
|
||||||
|
return {}
|
||||||
14
vrobbler/apps/puzzles/urls.py
Normal file
14
vrobbler/apps/puzzles/urls.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from puzzles import views
|
||||||
|
|
||||||
|
app_name = "puzzles"
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("puzzles/", views.PuzzleListView.as_view(), name="puzzle_list"),
|
||||||
|
path(
|
||||||
|
"puzzles/<slug:slug>/",
|
||||||
|
views.PuzzleDetailView.as_view(),
|
||||||
|
name="puzzle_detail",
|
||||||
|
),
|
||||||
|
]
|
||||||
11
vrobbler/apps/puzzles/views.py
Normal file
11
vrobbler/apps/puzzles/views.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from puzzles.models import Puzzle
|
||||||
|
|
||||||
|
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView
|
||||||
|
|
||||||
|
|
||||||
|
class PuzzleListView(ScrobbleableListView):
|
||||||
|
model = Puzzle
|
||||||
|
|
||||||
|
|
||||||
|
class PuzzleDetailView(ScrobbleableDetailView):
|
||||||
|
model = Puzzle
|
||||||
@ -34,6 +34,7 @@ SCROBBLE_CONTENT_URLS = {
|
|||||||
"-b": "https://www.amazon.com/",
|
"-b": "https://www.amazon.com/",
|
||||||
"-t": "https://app.todoist.com/app/task/{id}",
|
"-t": "https://app.todoist.com/app/task/{id}",
|
||||||
"-i": "https://www.youtube.com/watch?v=",
|
"-i": "https://www.youtube.com/watch?v=",
|
||||||
|
"-p": "https://www.ipdb.plus/IPDb/puzzle.php?id=",
|
||||||
}
|
}
|
||||||
|
|
||||||
EXCLUDE_FROM_NOW_PLAYING = ("GeoLocation",)
|
EXCLUDE_FROM_NOW_PLAYING = ("GeoLocation",)
|
||||||
@ -47,6 +48,7 @@ MANUAL_SCROBBLE_FNS = {
|
|||||||
"-u": "manual_scrobble_beer",
|
"-u": "manual_scrobble_beer",
|
||||||
"-w": "manual_scrobble_webpage",
|
"-w": "manual_scrobble_webpage",
|
||||||
"-t": "manual_scrobble_task",
|
"-t": "manual_scrobble_task",
|
||||||
|
"-p": "manual_scrobble_puzzle",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -211,3 +211,10 @@ class FoodLogData(JSONDataclass):
|
|||||||
details: Optional[str] = None
|
details: Optional[str] = None
|
||||||
rating: Optional[str] = None
|
rating: Optional[str] = None
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PuzzleLogData(JSONDataclass):
|
||||||
|
with_others: Optional[str] = None
|
||||||
|
rating: Optional[str] = None
|
||||||
|
notes: Optional[str] = None
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
# Generated by Django 4.2.19 on 2025-05-11 03:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("puzzles", "0001_initial"),
|
||||||
|
("scrobbles", "0068_scrobble_paper_alter_scrobble_media_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="scrobble",
|
||||||
|
name="puzzle",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||||
|
to="puzzles.puzzle",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="scrobble",
|
||||||
|
name="media_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("Video", "Video"),
|
||||||
|
("Track", "Track"),
|
||||||
|
("PodcastEpisode", "Podcast episode"),
|
||||||
|
("SportEvent", "Sport event"),
|
||||||
|
("Book", "Book"),
|
||||||
|
("Paper", "Paper"),
|
||||||
|
("VideoGame", "Video game"),
|
||||||
|
("BoardGame", "Board game"),
|
||||||
|
("GeoLocation", "GeoLocation"),
|
||||||
|
("Trail", "Trail"),
|
||||||
|
("Beer", "Beer"),
|
||||||
|
("Puzzle", "Puzzle"),
|
||||||
|
("Food", "Food"),
|
||||||
|
("Task", "Task"),
|
||||||
|
("WebPage", "Web Page"),
|
||||||
|
("LifeEvent", "Life event"),
|
||||||
|
("Mood", "Mood"),
|
||||||
|
("BrickSet", "Brick set"),
|
||||||
|
],
|
||||||
|
default="Video",
|
||||||
|
max_length=14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -36,20 +36,18 @@ from profiles.utils import (
|
|||||||
start_of_month,
|
start_of_month,
|
||||||
start_of_week,
|
start_of_week,
|
||||||
)
|
)
|
||||||
|
from puzzles.models import Puzzle
|
||||||
from scrobbles import dataclasses as logdata
|
from scrobbles import dataclasses as logdata
|
||||||
from scrobbles.constants import LONG_PLAY_MEDIA, MEDIA_END_PADDING_SECONDS
|
from scrobbles.constants import LONG_PLAY_MEDIA, MEDIA_END_PADDING_SECONDS
|
||||||
|
from scrobbles.notifications import NtfyNotification
|
||||||
from scrobbles.stats import build_charts
|
from scrobbles.stats import build_charts
|
||||||
from scrobbles.utils import (
|
from scrobbles.utils import get_file_md5_hash, media_class_to_foreign_key
|
||||||
get_file_md5_hash,
|
|
||||||
media_class_to_foreign_key,
|
|
||||||
)
|
|
||||||
from sports.models import SportEvent
|
from sports.models import SportEvent
|
||||||
from tasks.models import Task
|
from tasks.models import Task
|
||||||
from trails.models import Trail
|
from trails.models import Trail
|
||||||
from videogames import retroarch
|
from videogames import retroarch
|
||||||
from videogames.models import VideoGame
|
from videogames.models import VideoGame
|
||||||
from videos.models import Series, Video
|
from videos.models import Series, Video
|
||||||
from scrobbles.notifications import NtfyNotification
|
|
||||||
from webpages.models import WebPage
|
from webpages.models import WebPage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -508,6 +506,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
GEO_LOCATION = "GeoLocation", "GeoLocation"
|
GEO_LOCATION = "GeoLocation", "GeoLocation"
|
||||||
TRAIL = "Trail", "Trail"
|
TRAIL = "Trail", "Trail"
|
||||||
BEER = "Beer", "Beer"
|
BEER = "Beer", "Beer"
|
||||||
|
PUZZLE = "Puzzle", "Puzzle"
|
||||||
FOOD = "Food", "Food"
|
FOOD = "Food", "Food"
|
||||||
TASK = "Task", "Task"
|
TASK = "Task", "Task"
|
||||||
WEBPAGE = "WebPage", "Web Page"
|
WEBPAGE = "WebPage", "Web Page"
|
||||||
@ -536,6 +535,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
GeoLocation, on_delete=models.DO_NOTHING, **BNULL
|
GeoLocation, on_delete=models.DO_NOTHING, **BNULL
|
||||||
)
|
)
|
||||||
beer = models.ForeignKey(Beer, on_delete=models.DO_NOTHING, **BNULL)
|
beer = models.ForeignKey(Beer, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
|
puzzle = models.ForeignKey(Puzzle, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
food = models.ForeignKey(Food, on_delete=models.DO_NOTHING, **BNULL)
|
food = models.ForeignKey(Food, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
trail = models.ForeignKey(Trail, on_delete=models.DO_NOTHING, **BNULL)
|
trail = models.ForeignKey(Trail, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
task = models.ForeignKey(Task, on_delete=models.DO_NOTHING, **BNULL)
|
task = models.ForeignKey(Task, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
@ -948,6 +948,8 @@ class Scrobble(TimeStampedModel):
|
|||||||
media_obj = self.trail
|
media_obj = self.trail
|
||||||
if self.beer:
|
if self.beer:
|
||||||
media_obj = self.beer
|
media_obj = self.beer
|
||||||
|
if self.puzzle:
|
||||||
|
media_obj = self.puzzle
|
||||||
if self.task:
|
if self.task:
|
||||||
media_obj = self.task
|
media_obj = self.task
|
||||||
return media_obj
|
return media_obj
|
||||||
|
|||||||
@ -14,9 +14,9 @@ from locations.constants import LOCATION_PROVIDERS
|
|||||||
from locations.models import GeoLocation
|
from locations.models import GeoLocation
|
||||||
from music.constants import JELLYFIN_POST_KEYS, MOPIDY_POST_KEYS
|
from music.constants import JELLYFIN_POST_KEYS, MOPIDY_POST_KEYS
|
||||||
from music.models import Track
|
from music.models import Track
|
||||||
from music.utils import get_or_create_track
|
|
||||||
from podcasts.models import PodcastEpisode
|
from podcasts.models import PodcastEpisode
|
||||||
from podcasts.utils import parse_mopidy_uri
|
from podcasts.utils import parse_mopidy_uri
|
||||||
|
from puzzles.models import Puzzle
|
||||||
from scrobbles.constants import (
|
from scrobbles.constants import (
|
||||||
JELLYFIN_AUDIO_ITEM_TYPES,
|
JELLYFIN_AUDIO_ITEM_TYPES,
|
||||||
MANUAL_SCROBBLE_FNS,
|
MANUAL_SCROBBLE_FNS,
|
||||||
@ -679,3 +679,32 @@ def manual_scrobble_beer(
|
|||||||
|
|
||||||
# TODO Kick out a process to enrich the media here, and in every scrobble event
|
# TODO Kick out a process to enrich the media here, and in every scrobble event
|
||||||
return Scrobble.create_or_update(beer, user_id, scrobble_dict)
|
return Scrobble.create_or_update(beer, user_id, scrobble_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def manual_scrobble_puzzle(
|
||||||
|
ipdb_id: str, user_id: int, action: Optional[str] = None
|
||||||
|
):
|
||||||
|
puzzle = Puzzle.find_or_create(ipdb_id)
|
||||||
|
|
||||||
|
if not puzzle:
|
||||||
|
logger.error(f"No puzzle found for IPDB ID {ipdb_id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
scrobble_dict = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"timestamp": timezone.now(),
|
||||||
|
"playback_position_seconds": 0,
|
||||||
|
"source": "Vrobbler",
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
"[vrobbler-scrobble] puzzle scrobble request received",
|
||||||
|
extra={
|
||||||
|
"puzzle_id": puzzle.id,
|
||||||
|
"user_id": user_id,
|
||||||
|
"scrobble_dict": scrobble_dict,
|
||||||
|
"media_type": Scrobble.MediaType.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)
|
||||||
|
|||||||
@ -25,14 +25,13 @@ from rest_framework.decorators import (
|
|||||||
permission_classes,
|
permission_classes,
|
||||||
)
|
)
|
||||||
from rest_framework.parsers import MultiPartParser
|
from rest_framework.parsers import MultiPartParser
|
||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from scrobbles.api import serializers
|
from scrobbles.api import serializers
|
||||||
from scrobbles.constants import (
|
from scrobbles.constants import (
|
||||||
LONG_PLAY_MEDIA,
|
LONG_PLAY_MEDIA,
|
||||||
MANUAL_SCROBBLE_FNS,
|
MANUAL_SCROBBLE_FNS,
|
||||||
PLAY_AGAIN_MEDIA,
|
PLAY_AGAIN_MEDIA,
|
||||||
SCROBBLE_CONTENT_URLS,
|
|
||||||
)
|
)
|
||||||
from scrobbles.export import export_scrobbles
|
from scrobbles.export import export_scrobbles
|
||||||
from scrobbles.forms import ExportScrobbleForm, ScrobbleForm
|
from scrobbles.forms import ExportScrobbleForm, ScrobbleForm
|
||||||
|
|||||||
@ -147,6 +147,7 @@ INSTALLED_APPS = [
|
|||||||
"tasks",
|
"tasks",
|
||||||
"trails",
|
"trails",
|
||||||
"beers",
|
"beers",
|
||||||
|
"puzzles",
|
||||||
"foods",
|
"foods",
|
||||||
"lifeevents",
|
"lifeevents",
|
||||||
"moods",
|
"moods",
|
||||||
|
|||||||
Reference in New Issue
Block a user