Prechádzať zdrojové kódy

[tasks] Add tasks app

Colin Powell 9 mesiacov pred
rodič
commit
183469ebe5

+ 7 - 0
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",
     )

+ 49 - 0
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,
+            ),
+        ),
+    ]

+ 6 - 1
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):

+ 0 - 0
vrobbler/apps/tasks/__init__.py


+ 24 - 0
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,
+    ]

+ 24 - 0
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,
+    ]

+ 5 - 0
vrobbler/apps/tasks/apps.py

@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class TasksConfig(AppConfig):
+    name = "tasks"

+ 79 - 0
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,
+            },
+        ),
+    ]

+ 0 - 0
vrobbler/apps/tasks/migrations/__init__.py


+ 48 - 0
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"
+        )

+ 14 - 0
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/<slug:slug>/",
+        views.TaskDetailView.as_view(),
+        name="task_detail",
+    ),
+]

+ 11 - 0
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

+ 1 - 1
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 = [

+ 1 - 0
vrobbler/settings-testing.py

@@ -111,6 +111,7 @@ INSTALLED_APPS = [
     "videogames",
     "locations",
     "webpages",
+    "tasks",
     "trails",
     "lifeevents",
     "moods",

+ 1 - 0
vrobbler/settings.py

@@ -125,6 +125,7 @@ INSTALLED_APPS = [
     "videogames",
     "locations",
     "webpages",
+    "tasks",
     "trails",
     "lifeevents",
     "moods",

+ 73 - 0
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 %}
+<style>
+    .cover img {
+        width: 250px;
+    }
+
+    .cover {
+        float: left;
+        width: 252px;
+        padding: 0;
+    }
+
+    .summary {
+        float: left;
+        width: 600px;
+        margin-left: 10px;
+    }
+</style>
+{% endblock %}
+
+{% block lists %}
+
+<div class="row">
+    <div class="summary">
+        {% if object.description%}
+        <p>{{object.description|safe|linebreaks|truncatewords:160}}</p>
+        <hr />
+        {% endif %}
+        <p style="float:right;">
+            <a href="{{object.source_link}}"><img src="{% static "images/org-mode-logo.png" %}" width=35></a>
+        </p>
+    </div>
+</div>
+<div class="row">
+    <p>{{object.scrobble_set.count}} scrobbles</p>
+    <p>
+        <a href="{{object.get_start_url}}">Play again</a>
+    </p>
+</div>
+<div class="row">
+    <div class="col-md">
+        <h3>Last scrobbles</h3>
+        <div class="table-responsive">
+            <table class="table table-striped table-sm">
+                <thead>
+                    <tr>
+                        <th scope="col">Date</th>
+                        <th scope="col">Title</th>
+                        <th scope="col">Distance</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    {% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %}
+                    <tr>
+                        <td>{{scrobble.timestamp}}</td>
+                        <td>{{scrobble.logdata}}</td>
+                        <td>{{scrobble.media_obj.publisher}}</td>
+                        <td>{% if scrobble.screenshot%}<img src="{{scrobble.screenshot.url}}" width=250 />{% endif %}</td>
+                    </tr>
+                    {% endfor %}
+                </tbody>
+            </table>
+        </div>
+    </div>
+</div>
+{% endblock %}

+ 29 - 0
vrobbler/templates/tasks/task_list.html

@@ -0,0 +1,29 @@
+{% extends "base_list.html" %} {% block title %}Tasks{% endblock %} {% block
+head_extra %}
+<style>
+  dl {
+    width: 210px;
+    float: left;
+    margin-right: 10px;
+  }
+  dt a {
+    color: white;
+    text-decoration: none;
+    font-size: smaller;
+  }
+  img {
+    height: 200px;
+    width: 200px;
+    object-fit: cover;
+  }
+  dd .right {
+    float: right;
+  }
+</style>
+{% endblock %} {% block lists %}
+<div class="row">
+  <div class="col-md">
+    <div class="table-responsive">{% include "_scrobblable_list.html" %}</div>
+  </div>
+</div>
+{% endblock %}

+ 2 - 0
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")),