160 lines
4.8 KiB
Python
160 lines
4.8 KiB
Python
import json
|
|
from dataclasses import asdict, dataclass
|
|
from typing import Optional
|
|
|
|
from dataclass_wizard import JSONWizard
|
|
from django import forms
|
|
from django.contrib.auth import get_user_model
|
|
from django.db.models import Count
|
|
from people.models import Person
|
|
from scrobbles.forms import form_from_dataclass
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class ScrobbleLogDataEncoder(json.JSONEncoder):
|
|
def default(self, o):
|
|
try:
|
|
return o.__dict__
|
|
except AttributeError:
|
|
return {}
|
|
|
|
|
|
class ScrobbleLogDataDecoder(json.JSONDecoder):
|
|
def default(self, o):
|
|
return o.__dict__
|
|
|
|
|
|
class JSONDataclass(JSONWizard):
|
|
@property
|
|
def asdict(self):
|
|
return asdict(self)
|
|
|
|
@property
|
|
def json(self):
|
|
return json.dumps(self.asdict)
|
|
|
|
|
|
@dataclass
|
|
class BaseLogData(JSONDataclass):
|
|
description: Optional[str] = None
|
|
notes: Optional[list[str]] = None
|
|
|
|
_excluded_fields = {}
|
|
|
|
@classmethod
|
|
def form(cls):
|
|
return form_from_dataclass(cls)
|
|
|
|
@classmethod
|
|
def override_fields(cls) -> dict:
|
|
return {}
|
|
|
|
def notes_as_str(self, separator: str = " | ") -> str:
|
|
import html
|
|
import re
|
|
|
|
if not self.notes:
|
|
return ""
|
|
|
|
if isinstance(self.notes, dict):
|
|
lines = []
|
|
for ts, text in self.notes.items():
|
|
note_text = " ".join(text.strip().split())
|
|
lines.append(f"{ts}: {note_text}")
|
|
return separator.join(lines)
|
|
|
|
if isinstance(self.notes, str):
|
|
return html.escape(re.sub(r"\s*\[start:\d+\|end:\d+\]$", "", self.notes))
|
|
|
|
if isinstance(self.notes, list):
|
|
cleaned_notes = []
|
|
for note in self.notes:
|
|
if isinstance(note, dict):
|
|
timestamp, note_text = next(iter(note.items()))
|
|
note_text = " ".join(note_text.strip().split())
|
|
cleaned_notes.append(f"{timestamp}: {note_text}")
|
|
elif isinstance(note, str):
|
|
cleaned = html.escape(
|
|
re.sub(r"\s*\[start:\d+\|end:\d+\]$", "", note)
|
|
)
|
|
cleaned_notes.append(cleaned)
|
|
return separator.join(cleaned_notes)
|
|
|
|
return ""
|
|
|
|
def notes_as_html(self) -> str:
|
|
import html
|
|
from django.utils.safestring import mark_safe
|
|
|
|
if not self.notes:
|
|
return ""
|
|
|
|
html_notes = []
|
|
|
|
if isinstance(self.notes, dict):
|
|
for ts, text in self.notes.items():
|
|
note_text = " ".join(text.strip().split())
|
|
html_notes.append(
|
|
f'<div class="sticky-note">{ts}: {note_text}</div>'
|
|
)
|
|
elif isinstance(self.notes, str):
|
|
for line in self.notes.split("\n"):
|
|
if line.strip():
|
|
escaped_line = html.escape(line)
|
|
html_notes.append(f'<div class="sticky-note">{escaped_line}</div>')
|
|
elif isinstance(self.notes, list):
|
|
for note in self.notes:
|
|
if isinstance(note, dict):
|
|
timestamp, note_text = next(iter(note.items()))
|
|
note_text = " ".join(note_text.strip().split())
|
|
html_notes.append(
|
|
f'<div class="sticky-note">{timestamp}: {note_text}</div>'
|
|
)
|
|
elif isinstance(note, str):
|
|
for line in note.split("\n"):
|
|
if line.strip():
|
|
escaped_line = html.escape(line)
|
|
html_notes.append(
|
|
f'<div class="sticky-note">{escaped_line}</div>'
|
|
)
|
|
|
|
return mark_safe("".join(html_notes))
|
|
|
|
|
|
@dataclass
|
|
class LongPlayLogData(JSONDataclass):
|
|
long_play_complete: bool = False
|
|
|
|
|
|
@dataclass
|
|
class WithPeopleLogData(JSONDataclass):
|
|
with_people_ids: Optional[list[int]] = None
|
|
|
|
@property
|
|
def with_people(self) -> list["Person"]:
|
|
from people.models import Person
|
|
|
|
if not self.with_people_ids:
|
|
return []
|
|
return [Person.objects.filter(id=pid).first() for pid in self.with_people_ids]
|
|
|
|
@classmethod
|
|
def override_fields(cls) -> dict:
|
|
fields = {}
|
|
for base in cls.mro()[1:]:
|
|
if hasattr(base, "override_fields"):
|
|
base_fields = base.override_fields()
|
|
fields.update(base_fields)
|
|
custom_fields = {
|
|
"with_people_ids": forms.ModelMultipleChoiceField(
|
|
queryset=Person.objects.annotate(
|
|
scrobble_count=Count("scrobble_associations")
|
|
).order_by("-scrobble_count"),
|
|
required=False,
|
|
widget=forms.SelectMultiple(attrs={"size": 10}),
|
|
)
|
|
}
|
|
fields.update(custom_fields)
|
|
return fields
|