[tasks] Add tasks app
This commit is contained in:
@ -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",
|
||||
)
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -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
vrobbler/apps/tasks/__init__.py
Normal file
0
vrobbler/apps/tasks/__init__.py
Normal file
24
vrobbler/apps/tasks/admin
Normal file
24
vrobbler/apps/tasks/admin
Normal 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,
|
||||
]
|
||||
24
vrobbler/apps/tasks/admin.py
Normal file
24
vrobbler/apps/tasks/admin.py
Normal 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,
|
||||
]
|
||||
5
vrobbler/apps/tasks/apps.py
Normal file
5
vrobbler/apps/tasks/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TasksConfig(AppConfig):
|
||||
name = "tasks"
|
||||
79
vrobbler/apps/tasks/migrations/0001_initial.py
Normal file
79
vrobbler/apps/tasks/migrations/0001_initial.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
||||
0
vrobbler/apps/tasks/migrations/__init__.py
Normal file
0
vrobbler/apps/tasks/migrations/__init__.py
Normal file
48
vrobbler/apps/tasks/models.py
Normal file
48
vrobbler/apps/tasks/models.py
Normal 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"
|
||||
)
|
||||
14
vrobbler/apps/tasks/urls.py
Normal file
14
vrobbler/apps/tasks/urls.py
Normal 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",
|
||||
),
|
||||
]
|
||||
11
vrobbler/apps/tasks/views.py
Normal file
11
vrobbler/apps/tasks/views.py
Normal 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
|
||||
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
from trails import views
|
||||
|
||||
app_name = "trials"
|
||||
app_name = "trails"
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
@ -111,6 +111,7 @@ INSTALLED_APPS = [
|
||||
"videogames",
|
||||
"locations",
|
||||
"webpages",
|
||||
"tasks",
|
||||
"trails",
|
||||
"lifeevents",
|
||||
"moods",
|
||||
|
||||
@ -125,6 +125,7 @@ INSTALLED_APPS = [
|
||||
"videogames",
|
||||
"locations",
|
||||
"webpages",
|
||||
"tasks",
|
||||
"trails",
|
||||
"lifeevents",
|
||||
"moods",
|
||||
|
||||
73
vrobbler/templates/tasks/task_detail.html
Normal file
73
vrobbler/templates/tasks/task_detail.html
Normal 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 %}
|
||||
29
vrobbler/templates/tasks/task_list.html
Normal file
29
vrobbler/templates/tasks/task_list.html
Normal 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 %}
|
||||
@ -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")),
|
||||
|
||||
Reference in New Issue
Block a user