diff --git a/vrobbler/apps/scrobbles/admin.py b/vrobbler/apps/scrobbles/admin.py index 8551094..c5d8d77 100644 --- a/vrobbler/apps/scrobbles/admin.py +++ b/vrobbler/apps/scrobbles/admin.py @@ -23,6 +23,9 @@ class ScrobbleInline(admin.TabularInline): "sport_event", "board_game", "geo_location", + "task", + "mood", + "brickset", "trail", "web_page", "life_event", @@ -116,6 +119,10 @@ class ScrobbleAdmin(admin.ModelAdmin): "video_game", "board_game", "geo_location", + "task", + "mood", + "brickset", + "trail", "web_page", "life_event", ) diff --git a/vrobbler/apps/scrobbles/migrations/0064_scrobble_task_alter_scrobble_media_type.py b/vrobbler/apps/scrobbles/migrations/0064_scrobble_task_alter_scrobble_media_type.py new file mode 100644 index 0000000..076f0ab --- /dev/null +++ b/vrobbler/apps/scrobbles/migrations/0064_scrobble_task_alter_scrobble_media_type.py @@ -0,0 +1,49 @@ +# Generated by Django 4.2.16 on 2024-09-30 18:54 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("tasks", "0001_initial"), + ("scrobbles", "0063_scrobble_gpx_file"), + ] + + operations = [ + migrations.AddField( + model_name="scrobble", + name="task", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + to="tasks.task", + ), + ), + 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"), + ("Trail", "Trail"), + ("Task", "Task"), + ("WebPage", "Web Page"), + ("LifeEvent", "Life event"), + ("Mood", "Mood"), + ("BrickSet", "Brick set"), + ], + default="Video", + max_length=14, + ), + ), + ] diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index aee835e..2626151 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -38,10 +38,11 @@ from scrobbles.constants import LONG_PLAY_MEDIA from scrobbles.stats import build_charts from scrobbles.utils import media_class_to_foreign_key from sports.models import SportEvent +from tasks.models import Task +from trails.models import Trail from videogames import retroarch from videogames.models import VideoGame from videos.models import Series, Video -from trails.models import Trail from webpages.models import WebPage from vrobbler.apps.scrobbles.constants import MEDIA_END_PADDING_SECONDS @@ -485,6 +486,7 @@ class Scrobble(TimeStampedModel): BOARD_GAME = "BoardGame", "Board game" GEO_LOCATION = "GeoLocation", "GeoLocation" TRAIL = "Trail", "Trail" + TASK = "Task", "Task" WEBPAGE = "WebPage", "Web Page" LIFE_EVENT = "LifeEvent", "Life event" MOOD = "Mood", "Mood" @@ -510,6 +512,7 @@ class Scrobble(TimeStampedModel): GeoLocation, 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) web_page = models.ForeignKey(WebPage, on_delete=models.DO_NOTHING, **BNULL) life_event = models.ForeignKey( LifeEvent, on_delete=models.DO_NOTHING, **BNULL @@ -894,6 +897,8 @@ class Scrobble(TimeStampedModel): media_obj = self.brickset if self.trail: media_obj = self.trail + if self.task: + media_obj = self.task return media_obj def __str__(self): diff --git a/vrobbler/apps/tasks/__init__.py b/vrobbler/apps/tasks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vrobbler/apps/tasks/admin b/vrobbler/apps/tasks/admin new file mode 100644 index 0000000..50160f4 --- /dev/null +++ b/vrobbler/apps/tasks/admin @@ -0,0 +1,24 @@ +from django.contrib import admin + +from tasks.models import Task + +from scrobbles.admin import ScrobbleInline + + +class TaskInline(admin.TabularInline): + model = Task + extra = 0 + + +@admin.register(Task) +class TaskAdmin(admin.ModelAdmin): + date_hierarchy = "created" + list_display = ( + "uuid", + "title", + ) + ordering = ("-created",) + search_fields = ("title",) + inlines = [ + ScrobbleInline, + ] diff --git a/vrobbler/apps/tasks/admin.py b/vrobbler/apps/tasks/admin.py new file mode 100644 index 0000000..50160f4 --- /dev/null +++ b/vrobbler/apps/tasks/admin.py @@ -0,0 +1,24 @@ +from django.contrib import admin + +from tasks.models import Task + +from scrobbles.admin import ScrobbleInline + + +class TaskInline(admin.TabularInline): + model = Task + extra = 0 + + +@admin.register(Task) +class TaskAdmin(admin.ModelAdmin): + date_hierarchy = "created" + list_display = ( + "uuid", + "title", + ) + ordering = ("-created",) + search_fields = ("title",) + inlines = [ + ScrobbleInline, + ] diff --git a/vrobbler/apps/tasks/apps.py b/vrobbler/apps/tasks/apps.py new file mode 100644 index 0000000..5aadae0 --- /dev/null +++ b/vrobbler/apps/tasks/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TasksConfig(AppConfig): + name = "tasks" diff --git a/vrobbler/apps/tasks/migrations/0001_initial.py b/vrobbler/apps/tasks/migrations/0001_initial.py new file mode 100644 index 0000000..3bd563d --- /dev/null +++ b/vrobbler/apps/tasks/migrations/0001_initial.py @@ -0,0 +1,79 @@ +# Generated by Django 4.2.16 on 2024-09-30 18:54 + +from django.db import migrations, models +import django_extensions.db.fields +import taggit.managers +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("scrobbles", "0063_scrobble_gpx_file"), + ] + + operations = [ + migrations.CreateModel( + name="Task", + 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, + }, + ), + ] diff --git a/vrobbler/apps/tasks/migrations/__init__.py b/vrobbler/apps/tasks/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vrobbler/apps/tasks/models.py b/vrobbler/apps/tasks/models.py new file mode 100644 index 0000000..69e2f6e --- /dev/null +++ b/vrobbler/apps/tasks/models.py @@ -0,0 +1,48 @@ +from enum import Enum +from typing import Optional +from django.apps import apps +from django.db import models +from django.urls import reverse +from scrobbles.dataclasses import LongPlayLogData +from scrobbles.mixins import ScrobblableMixin + +BNULL = {"blank": True, "null": True} + + +class TaskLogData(LongPlayLogData): + serial_scrobble_id: Optional[int] + long_play_complete: bool = False + + +class TaskType(Enum): + WOODS = "Professional" + ROAD = "Amateur" + + +class Task(ScrobblableMixin): + """Basically a holder for task sources ... Shortcut, JIRA, Todoist, Org-mode + and any other otherwise generic tasks. + + """ + + description = models.TextField(**BNULL) + + def __str__(self): + return self.title + + def get_absolute_url(self): + return reverse("tasks:task_detail", kwargs={"slug": self.uuid}) + + @property + def logdata_cls(self): + return TaskLogData + + @classmethod + def find_or_create(cls, title: str) -> "Task": + return cls.objects.filter(title=title).first() + + def scrobbles(self, user_id): + Scrobble = apps.get_model("scrobbles", "Scrobble") + return Scrobble.objects.filter(user_id=user_id, task=self).order_by( + "-timestamp" + ) diff --git a/vrobbler/apps/tasks/urls.py b/vrobbler/apps/tasks/urls.py new file mode 100644 index 0000000..ce9bc36 --- /dev/null +++ b/vrobbler/apps/tasks/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +from tasks import views + +app_name = "tasks" + + +urlpatterns = [ + path("tasks/", views.TaskListView.as_view(), name="task_list"), + path( + "tasks//", + views.TaskDetailView.as_view(), + name="task_detail", + ), +] diff --git a/vrobbler/apps/tasks/views.py b/vrobbler/apps/tasks/views.py new file mode 100644 index 0000000..5e4bbe1 --- /dev/null +++ b/vrobbler/apps/tasks/views.py @@ -0,0 +1,11 @@ +from tasks.models import Task + +from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView + + +class TaskListView(ScrobbleableListView): + model = Task + + +class TaskDetailView(ScrobbleableDetailView): + model = Task diff --git a/vrobbler/apps/trails/urls.py b/vrobbler/apps/trails/urls.py index c9f51da..9005dba 100644 --- a/vrobbler/apps/trails/urls.py +++ b/vrobbler/apps/trails/urls.py @@ -1,7 +1,7 @@ from django.urls import path from trails import views -app_name = "trials" +app_name = "trails" urlpatterns = [ diff --git a/vrobbler/settings-testing.py b/vrobbler/settings-testing.py index 3ec5c12..c1d41e7 100644 --- a/vrobbler/settings-testing.py +++ b/vrobbler/settings-testing.py @@ -111,6 +111,7 @@ INSTALLED_APPS = [ "videogames", "locations", "webpages", + "tasks", "trails", "lifeevents", "moods", diff --git a/vrobbler/settings.py b/vrobbler/settings.py index c88cefd..23fc21b 100644 --- a/vrobbler/settings.py +++ b/vrobbler/settings.py @@ -125,6 +125,7 @@ INSTALLED_APPS = [ "videogames", "locations", "webpages", + "tasks", "trails", "lifeevents", "moods", diff --git a/vrobbler/templates/tasks/task_detail.html b/vrobbler/templates/tasks/task_detail.html new file mode 100644 index 0000000..aee9716 --- /dev/null +++ b/vrobbler/templates/tasks/task_detail.html @@ -0,0 +1,73 @@ +{% extends "base_list.html" %} +{% load mathfilters %} +{% load static %} +{% load naturalduration %} + +{% block title %}{{object.title}}{% endblock %} + +{% block head_extra %} + +{% endblock %} + +{% block lists %} + +
+
+ {% if object.description%} +

{{object.description|safe|linebreaks|truncatewords:160}}

+
+ {% endif %} +

+ +

+
+
+
+

{{object.scrobble_set.count}} scrobbles

+

+ Play again +

+
+
+
+

Last scrobbles

+
+ + + + + + + + + + {% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %} + + + + + + + {% endfor %} + +
DateTitleDistance
{{scrobble.timestamp}}{{scrobble.logdata}}{{scrobble.media_obj.publisher}}{% if scrobble.screenshot%}{% endif %}
+
+
+
+{% endblock %} diff --git a/vrobbler/templates/tasks/task_list.html b/vrobbler/templates/tasks/task_list.html new file mode 100644 index 0000000..641a665 --- /dev/null +++ b/vrobbler/templates/tasks/task_list.html @@ -0,0 +1,29 @@ +{% extends "base_list.html" %} {% block title %}Tasks{% endblock %} {% block +head_extra %} + +{% endblock %} {% block lists %} +
+
+
{% include "_scrobblable_list.html" %}
+
+
+{% endblock %} diff --git a/vrobbler/urls.py b/vrobbler/urls.py index cb5858d..ba8327e 100644 --- a/vrobbler/urls.py +++ b/vrobbler/urls.py @@ -13,6 +13,7 @@ from vrobbler.apps.videogames import urls as videogame_urls from vrobbler.apps.boardgames import urls as boardgame_urls from vrobbler.apps.locations import urls as locations_urls from vrobbler.apps.lifeevents import urls as lifeevents_urls +from vrobbler.apps.tasks import urls as tasks_urls from vrobbler.apps.trails import urls as trails_urls from vrobbler.apps.webpages import urls as webpages_urls from vrobbler.apps.moods import urls as moods_urls @@ -75,6 +76,7 @@ urlpatterns = [ path("", include(sports_urls, namespace="sports")), path("", include(locations_urls, namespace="locations")), path("", include(trails_urls, namespace="trails")), + path("", include(tasks_urls, namespace="tasks")), path("", include(webpages_urls, namespace="webpages")), path("", include(podcast_urls, namespace="podcasts")), path("", include(lifeevents_urls, namespace="life-events")),