Internal
Public Access
Fix overdue detection ignoring due_time, harden SW precache installs
Task.is_overdue only compared due_date to today, so a task due at 9am stayed "not overdue" until midnight regardless of due_time (Gitea #7). Now factors in due_time when the due date is today, mirrored in the offline app's isTaskOverdue(). Also hardened the service worker's install step: cache.addAll() lets the browser's HTTP cache satisfy each precache fetch, and since static assets here send no Cache-Control/ETag (just Last-Modified), a CACHE_NAME bump wasn't reliably guaranteed to pick up fresh files. Forcing {cache: 'reload'} on each precache fetch fixes that. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c454423d4d
commit
d67a7e4d8f
@@ -117,7 +117,14 @@ function escapeHtml(str) {
|
||||
function isTaskOverdue(task) {
|
||||
// Mirrors Task.is_overdue in tasks/models.py.
|
||||
if (!task.due_date || task.status === 'completed' || task.status === 'cancelled') return false;
|
||||
return task.due_date < todayLocalStr();
|
||||
const today = todayLocalStr();
|
||||
if (task.due_date < today) return true;
|
||||
if (task.due_date === today && task.due_time) {
|
||||
const now = new Date();
|
||||
const nowStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
|
||||
return task.due_time < nowStr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function renderTaskItem(task, tagsBySyncId) {
|
||||
|
||||
+7
-4
@@ -155,16 +155,19 @@ class Task(models.Model):
|
||||
from django.utils import timezone
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
# Get current date in user's timezone
|
||||
# Get current date/time in user's timezone
|
||||
try:
|
||||
user_tz = ZoneInfo(self.user.timezone)
|
||||
user_now = timezone.now().astimezone(user_tz)
|
||||
user_today = user_now.date()
|
||||
except (Exception,):
|
||||
# Fall back to UTC if user timezone is invalid or missing
|
||||
user_today = timezone.now().date()
|
||||
user_now = timezone.now()
|
||||
|
||||
return self.due_date < user_today
|
||||
if self.due_date < user_now.date():
|
||||
return True
|
||||
if self.due_date == user_now.date() and self.due_time:
|
||||
return self.due_time < user_now.time()
|
||||
return False
|
||||
return False
|
||||
|
||||
@property
|
||||
|
||||
+49
-1
@@ -1,7 +1,9 @@
|
||||
from datetime import date
|
||||
from datetime import date, datetime, time as dt_time
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone as django_timezone
|
||||
|
||||
from tasks.models import Task
|
||||
|
||||
@@ -85,3 +87,49 @@ class CustomRecurrenceTests(TestCase):
|
||||
parsed = task.parsed_custom_recurrence
|
||||
self.assertIsNone(parsed['freq'])
|
||||
self.assertEqual(parsed['byweekday'], [])
|
||||
|
||||
|
||||
class IsOverdueTests(TestCase):
|
||||
"""Tests for Task.is_overdue, which must account for due_time, not just due_date (Gitea #7)."""
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username='overdueuser',
|
||||
email='overdueuser@example.com',
|
||||
password='testpass123',
|
||||
)
|
||||
|
||||
def make_task(self, **kwargs):
|
||||
defaults = {'user': self.user, 'title': 'Test task', 'status': 'pending'}
|
||||
defaults.update(kwargs)
|
||||
return Task.objects.create(**defaults)
|
||||
|
||||
@patch('django.utils.timezone.now')
|
||||
def test_not_overdue_before_due_time_same_day(self, mock_now):
|
||||
mock_now.return_value = django_timezone.make_aware(datetime(2026, 1, 15, 8, 0, 0))
|
||||
task = self.make_task(due_date=date(2026, 1, 15), due_time=dt_time(17, 0, 0))
|
||||
self.assertFalse(task.is_overdue)
|
||||
|
||||
@patch('django.utils.timezone.now')
|
||||
def test_overdue_after_due_time_same_day(self, mock_now):
|
||||
mock_now.return_value = django_timezone.make_aware(datetime(2026, 1, 15, 18, 0, 0))
|
||||
task = self.make_task(due_date=date(2026, 1, 15), due_time=dt_time(17, 0, 0))
|
||||
self.assertTrue(task.is_overdue)
|
||||
|
||||
@patch('django.utils.timezone.now')
|
||||
def test_not_overdue_same_day_without_due_time(self, mock_now):
|
||||
mock_now.return_value = django_timezone.make_aware(datetime(2026, 1, 15, 23, 59, 0))
|
||||
task = self.make_task(due_date=date(2026, 1, 15), due_time=None)
|
||||
self.assertFalse(task.is_overdue)
|
||||
|
||||
@patch('django.utils.timezone.now')
|
||||
def test_overdue_once_date_has_passed(self, mock_now):
|
||||
mock_now.return_value = django_timezone.make_aware(datetime(2026, 1, 16, 0, 1, 0))
|
||||
task = self.make_task(due_date=date(2026, 1, 15), due_time=dt_time(23, 0, 0))
|
||||
self.assertTrue(task.is_overdue)
|
||||
|
||||
@patch('django.utils.timezone.now')
|
||||
def test_completed_task_never_overdue(self, mock_now):
|
||||
mock_now.return_value = django_timezone.make_aware(datetime(2026, 1, 15, 23, 0, 0))
|
||||
task = self.make_task(due_date=date(2026, 1, 1), due_time=dt_time(9, 0, 0), status='completed')
|
||||
self.assertFalse(task.is_overdue)
|
||||
|
||||
+14
-2
@@ -1,4 +1,4 @@
|
||||
{% load static %}const CACHE_NAME = 'keepitgoing-shell-v8';
|
||||
{% load static %}const CACHE_NAME = 'keepitgoing-shell-v10';
|
||||
const OFFLINE_URL = '{% url "offline" %}';
|
||||
const OFFLINE_TASKS_URL = '{% url "offline-tasks" %}';
|
||||
|
||||
@@ -18,7 +18,19 @@ const PRECACHE_URLS = [
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))
|
||||
caches.open(CACHE_NAME).then((cache) =>
|
||||
// cache.addAll() lets the browser's own HTTP cache satisfy each
|
||||
// fetch - static assets here have no Cache-Control/ETag (only
|
||||
// Last-Modified), so a heuristically-cached stale response can
|
||||
// silently win even on a CACHE_NAME bump. { cache: 'reload' }
|
||||
// forces a real network round-trip so precache always reflects
|
||||
// what the server currently serves.
|
||||
Promise.all(
|
||||
PRECACHE_URLS.map((url) =>
|
||||
fetch(url, { cache: 'reload' }).then((response) => cache.put(url, response))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user