[tasks] Add tasks app

This commit is contained in:
2024-09-30 14:55:02 -04:00
parent bbe8149e6c
commit 183469ebe5
18 changed files with 374 additions and 2 deletions

View File

@ -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",
)

View File

@ -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,
),
),
]

View File

@ -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):

View File

24
vrobbler/apps/tasks/admin Normal file
View File

@ -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,
]

View File

@ -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,
]

View File

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

View File

@ -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,
},
),
]

View File

@ -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"
)

View File

@ -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",
),
]

View File

@ -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

View File

@ -1,7 +1,7 @@
from django.urls import path
from trails import views
app_name = "trials"
app_name = "trails"
urlpatterns = [

View File

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

View File

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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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")),