[moods] Add moods
This commit is contained in:
3
data/moods.json
Normal file
3
data/moods.json
Normal file
File diff suppressed because one or more lines are too long
@ -12,8 +12,8 @@ from django.urls import reverse
|
|||||||
from django_extensions.db.models import TimeStampedModel
|
from django_extensions.db.models import TimeStampedModel
|
||||||
from imagekit.models import ImageSpecField
|
from imagekit.models import ImageSpecField
|
||||||
from imagekit.processors import ResizeToFit
|
from imagekit.processors import ResizeToFit
|
||||||
|
from scrobbles.dataclasses import BoardGameLogData
|
||||||
from scrobbles.mixins import ScrobblableMixin
|
from scrobbles.mixins import ScrobblableMixin
|
||||||
from vrobbler.apps.boardgames.bgg import lookup_boardgame_id_from_bgg
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
BNULL = {"blank": True, "null": True}
|
BNULL = {"blank": True, "null": True}
|
||||||
@ -96,6 +96,10 @@ class BoardGame(ScrobblableMixin):
|
|||||||
"boardgames:boardgame_detail", kwargs={"slug": self.uuid}
|
"boardgames:boardgame_detail", kwargs={"slug": self.uuid}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logdata_cls(self):
|
||||||
|
return BoardGameLogData
|
||||||
|
|
||||||
def primary_image_url(self) -> str:
|
def primary_image_url(self) -> str:
|
||||||
url = ""
|
url = ""
|
||||||
if self.cover:
|
if self.cover:
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from django.apps import apps
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
import pendulum
|
import pendulum
|
||||||
from scrobbles.dataclasses import LifeEventMetadata
|
from scrobbles.dataclasses import LifeEventLogData
|
||||||
from scrobbles.mixins import ScrobblableMixin
|
from scrobbles.mixins import ScrobblableMixin
|
||||||
|
|
||||||
BNULL = {"blank": True, "null": True}
|
BNULL = {"blank": True, "null": True}
|
||||||
@ -22,8 +22,8 @@ class LifeEvent(ScrobblableMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def metadata_class(self):
|
def logdata_cls(self):
|
||||||
return LifeEventMetadata
|
return LifeEventLogData
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_or_create(cls, title: str) -> "LifeEvent":
|
def find_or_create(cls, title: str) -> "LifeEvent":
|
||||||
|
|||||||
23
vrobbler/apps/moods/admin.py
Normal file
23
vrobbler/apps/moods/admin.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from moods.models import Mood
|
||||||
|
from scrobbles.admin import ScrobbleInline
|
||||||
|
|
||||||
|
|
||||||
|
class MoodInline(admin.TabularInline):
|
||||||
|
model = Mood
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Mood)
|
||||||
|
class MoodAdmin(admin.ModelAdmin):
|
||||||
|
date_hierarchy = "created"
|
||||||
|
list_display = (
|
||||||
|
"uuid",
|
||||||
|
"title",
|
||||||
|
)
|
||||||
|
ordering = ("-created",)
|
||||||
|
search_fields = ("title",)
|
||||||
|
inlines = [
|
||||||
|
ScrobbleInline,
|
||||||
|
]
|
||||||
5
vrobbler/apps/moods/apps.py
Normal file
5
vrobbler/apps/moods/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class MoodsConfig(AppConfig):
|
||||||
|
name = "moods"
|
||||||
79
vrobbler/apps/moods/migrations/0001_initial.py
Normal file
79
vrobbler/apps/moods/migrations/0001_initial.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Generated by Django 4.2.13 on 2024-08-10 20:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django_extensions.db.fields
|
||||||
|
import taggit.managers
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("scrobbles", "0056_scrobble_life_event_alter_scrobble_media_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Mood",
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
("description", models.TextField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"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/moods/migrations/__init__.py
Normal file
0
vrobbler/apps/moods/migrations/__init__.py
Normal file
28
vrobbler/apps/moods/models.py
Normal file
28
vrobbler/apps/moods/models.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import logging
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from scrobbles.mixins import ScrobblableMixin
|
||||||
|
from vrobbler.apps.scrobbles.dataclasses import MoodLogData
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
BNULL = {"blank": True, "null": True}
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class Mood(ScrobblableMixin):
|
||||||
|
description = models.TextField(**BNULL)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.title:
|
||||||
|
return self.title
|
||||||
|
return str(self.uuid)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse("moods:mood-detail", kwargs={"slug": self.uuid})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logdata_cls(self):
|
||||||
|
return MoodLogData
|
||||||
14
vrobbler/apps/moods/urls.py
Normal file
14
vrobbler/apps/moods/urls.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from moods import views
|
||||||
|
|
||||||
|
app_name = "moods"
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("mood/", views.MoodListView.as_view(), name="mood-list"),
|
||||||
|
path(
|
||||||
|
"mood/<slug:slug>/",
|
||||||
|
views.MoodDetailView.as_view(),
|
||||||
|
name="mood-detail",
|
||||||
|
),
|
||||||
|
]
|
||||||
12
vrobbler/apps/moods/views.py
Normal file
12
vrobbler/apps/moods/views.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.db.models import Count
|
||||||
|
from django.views import generic
|
||||||
|
from moods.models import Mood
|
||||||
|
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView
|
||||||
|
|
||||||
|
|
||||||
|
class MoodListView(ScrobbleableListView):
|
||||||
|
model = Mood
|
||||||
|
|
||||||
|
|
||||||
|
class MoodDetailView(ScrobbleableDetailView):
|
||||||
|
model = Mood
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
from functools import cached_property
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
@ -10,17 +11,17 @@ from locations.models import GeoLocation
|
|||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class ScrobbleMetadataEncoder(json.JSONEncoder):
|
class ScrobbleLogDataEncoder(json.JSONEncoder):
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
return o.__dict__
|
return o.__dict__
|
||||||
|
|
||||||
|
|
||||||
class ScrobbleMetadataDecoder(json.JSONDecoder):
|
class ScrobbleLogDataDecoder(json.JSONDecoder):
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
return o.__dict__
|
return o.__dict__
|
||||||
|
|
||||||
|
|
||||||
class JSONMetadata(JSONWizard):
|
class JSONDataclass(JSONWizard):
|
||||||
@property
|
@property
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
@ -29,19 +30,9 @@ class JSONMetadata(JSONWizard):
|
|||||||
def json(self):
|
def json(self):
|
||||||
return json.dumps(self.asdict)
|
return json.dumps(self.asdict)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, env):
|
|
||||||
return cls(
|
|
||||||
**{
|
|
||||||
k: v
|
|
||||||
for k, v in env.items()
|
|
||||||
if k in inspect.signature(cls).parameters
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BoardGameScore(JSONMetadata):
|
class BoardGameScoreLogData(JSONDataclass):
|
||||||
user_id: Optional[int] = None
|
user_id: Optional[int] = None
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
bgg_username: Optional[str] = None
|
bgg_username: Optional[str] = None
|
||||||
@ -53,19 +44,22 @@ class BoardGameScore(JSONMetadata):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BoardGameMetadata(JSONMetadata):
|
class BoardGameLogData(JSONDataclass):
|
||||||
players: Optional[list[BoardGameScore]] = None
|
players: Optional[list[BoardGameScoreLogData]] = None
|
||||||
|
location: Optional[str] = None
|
||||||
|
geo_location_id: Optional[int] = None
|
||||||
difficulty: Optional[int] = None
|
difficulty: Optional[int] = None
|
||||||
solo: Optional[bool] = None
|
solo: Optional[bool] = None
|
||||||
two_handed: Optional[bool] = None
|
two_handed: Optional[bool] = None
|
||||||
location: Optional[str] = None
|
|
||||||
|
|
||||||
def geo_location(self):
|
@cached_property
|
||||||
return GeoLocation.objects.filter(id=self.geo_location_id).first()
|
def geo_location(self) -> Optional[GeoLocation]:
|
||||||
|
if self.geo_location_id:
|
||||||
|
return GeoLocation.objects.filter(id=self.geo_location_id).first()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BookPageMetadata(JSONMetadata):
|
class BookPageLogData(JSONDataclass):
|
||||||
page_number: Optional[int] = None
|
page_number: Optional[int] = None
|
||||||
end_ts: Optional[int] = None
|
end_ts: Optional[int] = None
|
||||||
start_ts: Optional[int] = None
|
start_ts: Optional[int] = None
|
||||||
@ -73,14 +67,14 @@ class BookPageMetadata(JSONMetadata):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BookMetadata(JSONMetadata):
|
class BookLogData(JSONDataclass):
|
||||||
koreader_hash: Optional[str]
|
koreader_hash: Optional[str]
|
||||||
pages_read: Optional[int]
|
pages_read: Optional[int]
|
||||||
page_data: Optional[list[BookPageMetadata]]
|
page_data: Optional[list[BookPageLogData]]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LifeEventMetadata(JSONMetadata):
|
class LifeEventLogData(JSONDataclass):
|
||||||
participant_user_ids: Optional[list[int]] = None
|
participant_user_ids: Optional[list[int]] = None
|
||||||
participant_names: Optional[list[str]] = None
|
participant_names: Optional[list[str]] = None
|
||||||
location: Optional[str] = None
|
location: Optional[str] = None
|
||||||
@ -108,7 +102,12 @@ class LifeEventMetadata(JSONMetadata):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VideoMetadata(JSONMetadata):
|
class MoodLogData(JSONDataclass):
|
||||||
|
reasons: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VideoMetadata(JSONDataclass):
|
||||||
title: str
|
title: str
|
||||||
video_type: str
|
video_type: str
|
||||||
run_time_seconds: int
|
run_time_seconds: int
|
||||||
|
|||||||
@ -0,0 +1,57 @@
|
|||||||
|
# Generated by Django 4.2.13 on 2024-08-10 20:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import scrobbles.dataclasses
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("moods", "0001_initial"),
|
||||||
|
("scrobbles", "0056_scrobble_life_event_alter_scrobble_media_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="scrobble",
|
||||||
|
name="mood",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||||
|
to="moods.mood",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="scrobble",
|
||||||
|
name="log",
|
||||||
|
field=models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
decoder=scrobbles.dataclasses.ScrobbleLogDataDecoder,
|
||||||
|
encoder=scrobbles.dataclasses.ScrobbleLogDataEncoder,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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"),
|
||||||
|
],
|
||||||
|
default="Video",
|
||||||
|
max_length=14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import pytz
|
|
||||||
import calendar
|
import calendar
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
@ -7,6 +6,7 @@ from typing import Iterable, Optional
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import pendulum
|
import pendulum
|
||||||
|
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
|
||||||
@ -19,7 +19,9 @@ from django.utils.functional import cached_property
|
|||||||
from django_extensions.db.models import TimeStampedModel
|
from django_extensions.db.models import TimeStampedModel
|
||||||
from imagekit.models import ImageSpecField
|
from imagekit.models import ImageSpecField
|
||||||
from imagekit.processors import ResizeToFit
|
from imagekit.processors import ResizeToFit
|
||||||
|
from lifeevents.models import LifeEvent
|
||||||
from locations.models import GeoLocation
|
from locations.models import GeoLocation
|
||||||
|
from moods.models import Mood
|
||||||
from music.lastfm import LastFM
|
from music.lastfm import LastFM
|
||||||
from music.models import Artist, Track
|
from music.models import Artist, Track
|
||||||
from podcasts.models import PodcastEpisode
|
from podcasts.models import PodcastEpisode
|
||||||
@ -31,6 +33,7 @@ from profiles.utils import (
|
|||||||
start_of_month,
|
start_of_month,
|
||||||
start_of_week,
|
start_of_week,
|
||||||
)
|
)
|
||||||
|
from scrobbles import dataclasses as logdata
|
||||||
from scrobbles.constants import LONG_PLAY_MEDIA
|
from scrobbles.constants import LONG_PLAY_MEDIA
|
||||||
from scrobbles.stats import build_charts
|
from scrobbles.stats import build_charts
|
||||||
from scrobbles.utils import media_class_to_foreign_key
|
from scrobbles.utils import media_class_to_foreign_key
|
||||||
@ -38,17 +41,7 @@ from sports.models import SportEvent
|
|||||||
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.dataclasses import (
|
|
||||||
BoardGameMetadata,
|
|
||||||
BookMetadata,
|
|
||||||
JSONMetadata,
|
|
||||||
LifeEventMetadata,
|
|
||||||
ScrobbleMetadataDecoder,
|
|
||||||
ScrobbleMetadataEncoder,
|
|
||||||
VideoMetadata,
|
|
||||||
)
|
|
||||||
from webpages.models import WebPage
|
from webpages.models import WebPage
|
||||||
from lifeevents.models import LifeEvent
|
|
||||||
|
|
||||||
from vrobbler.apps.scrobbles.constants import MEDIA_END_PADDING_SECONDS
|
from vrobbler.apps.scrobbles.constants import MEDIA_END_PADDING_SECONDS
|
||||||
|
|
||||||
@ -492,6 +485,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
GEO_LOCATION = "GeoLocation", "GeoLocation"
|
GEO_LOCATION = "GeoLocation", "GeoLocation"
|
||||||
WEBPAGE = "WebPage", "Web Page"
|
WEBPAGE = "WebPage", "Web Page"
|
||||||
LIFE_EVENT = "LifeEvent", "Life event"
|
LIFE_EVENT = "LifeEvent", "Life event"
|
||||||
|
MOOD = "Mood", "Mood"
|
||||||
|
|
||||||
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)
|
||||||
@ -516,6 +510,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
life_event = models.ForeignKey(
|
life_event = models.ForeignKey(
|
||||||
LifeEvent, on_delete=models.DO_NOTHING, **BNULL
|
LifeEvent, on_delete=models.DO_NOTHING, **BNULL
|
||||||
)
|
)
|
||||||
|
mood = models.ForeignKey(Mood, 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
|
||||||
)
|
)
|
||||||
@ -537,8 +532,8 @@ class Scrobble(TimeStampedModel):
|
|||||||
source = models.CharField(max_length=255, **BNULL)
|
source = models.CharField(max_length=255, **BNULL)
|
||||||
log = models.JSONField(
|
log = models.JSONField(
|
||||||
**BNULL,
|
**BNULL,
|
||||||
encoder=ScrobbleMetadataEncoder,
|
encoder=logdata.ScrobbleLogDataEncoder,
|
||||||
decoder=ScrobbleMetadataDecoder,
|
decoder=logdata.ScrobbleLogDataDecoder,
|
||||||
)
|
)
|
||||||
timezone = models.CharField(max_length=50, **BNULL)
|
timezone = models.CharField(max_length=50, **BNULL)
|
||||||
|
|
||||||
@ -582,7 +577,8 @@ class Scrobble(TimeStampedModel):
|
|||||||
# Microseconds mess up Django's filtering, and we don't need be that specific
|
# Microseconds mess up Django's filtering, and we don't need be that specific
|
||||||
if self.timestamp:
|
if self.timestamp:
|
||||||
self.timestamp = self.timestamp.replace(microsecond=0)
|
self.timestamp = self.timestamp.replace(microsecond=0)
|
||||||
self.media_type = self.MediaType(self.media_obj.__class__.__name__)
|
if self.media_obj:
|
||||||
|
self.media_type = self.MediaType(self.media_obj.__class__.__name__)
|
||||||
|
|
||||||
return super(Scrobble, self).save(*args, **kwargs)
|
return super(Scrobble, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@ -608,25 +604,15 @@ class Scrobble(TimeStampedModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def metadata(self) -> Optional[JSONMetadata]:
|
def logdata(self) -> dict:
|
||||||
metadata_cls = None
|
if not self.media_obj.logdata_cls:
|
||||||
if self.media_type == self.MediaType.LIFE_EVENT:
|
|
||||||
metadata_cls = LifeEventMetadata
|
|
||||||
if self.media_type == self.MediaType.BOARD_GAME:
|
|
||||||
metadata_cls = BoardGameMetadata
|
|
||||||
if self.media_type == self.MediaType.VIDEO:
|
|
||||||
metadata_cls = VideoMetadata
|
|
||||||
if self.media_type == self.MediaType.BOOK:
|
|
||||||
metadata_cls = BookMetadata
|
|
||||||
|
|
||||||
if not metadata_cls:
|
|
||||||
logger.warn(
|
logger.warn(
|
||||||
f"Media type has no metadata class",
|
f"Media type has no log data class",
|
||||||
extra={"media_type": self.media_type, "scrobble_id": self.id},
|
extra={"media_type": self.media_type, "scrobble_id": self.id},
|
||||||
)
|
)
|
||||||
return None
|
return {}
|
||||||
|
|
||||||
return metadata_cls.from_dict(self.log)
|
return self.media_obj.logdata_cls.from_dict(self.log)
|
||||||
|
|
||||||
def redirect_url(self, user_id) -> str:
|
def redirect_url(self, user_id) -> str:
|
||||||
user = User.objects.filter(id=user_id).first()
|
user = User.objects.filter(id=user_id).first()
|
||||||
@ -864,6 +850,8 @@ class Scrobble(TimeStampedModel):
|
|||||||
media_obj = self.web_page
|
media_obj = self.web_page
|
||||||
if self.life_event:
|
if self.life_event:
|
||||||
media_obj = self.life_event
|
media_obj = self.life_event
|
||||||
|
if self.mood:
|
||||||
|
media_obj = self.mood
|
||||||
return media_obj
|
return media_obj
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from django.apps import apps
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db.models import Q
|
from django.db.models import Count, Q
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.http import FileResponse, HttpResponseRedirect, JsonResponse
|
from django.http import FileResponse, HttpResponseRedirect, JsonResponse
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -60,6 +60,37 @@ from scrobbles.utils import (
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ScrobbleableListView(ListView):
|
||||||
|
model = None
|
||||||
|
paginate_by = 20
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
if not self.request.user.is_anonymous:
|
||||||
|
queryset = queryset.annotate(
|
||||||
|
scrobble_count=Count("scrobble"),
|
||||||
|
filter=Q(user=self.request.user),
|
||||||
|
).order_by("-scrobble_count")
|
||||||
|
else:
|
||||||
|
queryset = queryset.annotate(
|
||||||
|
scrobble_count=Count("scrobble")
|
||||||
|
).order_by("-scrobble_count")
|
||||||
|
|
||||||
|
|
||||||
|
class ScrobbleableDetailView(DetailView):
|
||||||
|
model = None
|
||||||
|
slug_field = "uuid"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context_data = super().get_context_data(**kwargs)
|
||||||
|
context_data["scrobbles"] = list()
|
||||||
|
if not self.request.user.is_anonymous:
|
||||||
|
context_data["scrobbles"] = self.object.scrobbles(
|
||||||
|
self.request.user
|
||||||
|
)
|
||||||
|
return context_data
|
||||||
|
|
||||||
|
|
||||||
class RecentScrobbleList(ListView):
|
class RecentScrobbleList(ListView):
|
||||||
model = Scrobble
|
model = Scrobble
|
||||||
|
|
||||||
|
|||||||
@ -125,6 +125,7 @@ INSTALLED_APPS = [
|
|||||||
"locations",
|
"locations",
|
||||||
"webpages",
|
"webpages",
|
||||||
"lifeevents",
|
"lifeevents",
|
||||||
|
"moods",
|
||||||
"mathfilters",
|
"mathfilters",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"allauth",
|
"allauth",
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from vrobbler.apps.boardgames import urls as boardgame_urls
|
|||||||
from vrobbler.apps.locations import urls as locations_urls
|
from vrobbler.apps.locations import urls as locations_urls
|
||||||
from vrobbler.apps.lifeevents import urls as lifeevents_urls
|
from vrobbler.apps.lifeevents import urls as lifeevents_urls
|
||||||
from vrobbler.apps.webpages import urls as webpages_urls
|
from vrobbler.apps.webpages import urls as webpages_urls
|
||||||
|
from vrobbler.apps.moods import urls as moods_urls
|
||||||
from vrobbler.apps.music.api.views import (
|
from vrobbler.apps.music.api.views import (
|
||||||
AlbumViewSet,
|
AlbumViewSet,
|
||||||
ArtistViewSet,
|
ArtistViewSet,
|
||||||
@ -73,6 +74,7 @@ urlpatterns = [
|
|||||||
path("", include(webpages_urls, namespace="webpages")),
|
path("", include(webpages_urls, namespace="webpages")),
|
||||||
path("", include(podcast_urls, namespace="podcasts")),
|
path("", include(podcast_urls, namespace="podcasts")),
|
||||||
path("", include(lifeevents_urls, namespace="life-events")),
|
path("", include(lifeevents_urls, namespace="life-events")),
|
||||||
|
path("", include(moods_urls, namespace="moods")),
|
||||||
path("", include(scrobble_urls, namespace="scrobbles")),
|
path("", include(scrobble_urls, namespace="scrobbles")),
|
||||||
path(
|
path(
|
||||||
"", scrobbles_views.RecentScrobbleList.as_view(), name="vrobbler-home"
|
"", scrobbles_views.RecentScrobbleList.as_view(), name="vrobbler-home"
|
||||||
|
|||||||
Reference in New Issue
Block a user