Merge pull request #156 from DRYTRIX/151-v340-time-entry-templates-500-error

Fix: Add missing timeago template filter causing 500 error
This commit is contained in:
Dries Peeters
2025-10-25 06:53:44 +02:00
committed by GitHub
3 changed files with 246 additions and 0 deletions

View File

@@ -104,3 +104,60 @@ def register_template_filters(app):
return f"{float(value):,.2f}"
except Exception:
return str(value)
@app.template_filter('timeago')
def timeago_filter(dt):
"""Convert a datetime to a 'time ago' string (e.g., '2 hours ago')"""
if dt is None:
return ""
# Import here to avoid circular imports
from datetime import datetime, timezone
# Ensure we're working with a timezone-aware datetime
if dt.tzinfo is None:
# Assume UTC if no timezone info
dt = dt.replace(tzinfo=timezone.utc)
# Get current time in UTC
now = datetime.now(timezone.utc)
# Calculate difference
diff = now - dt
# Convert to seconds
seconds = diff.total_seconds()
# Handle future dates
if seconds < 0:
return "just now"
# Calculate time units
minutes = seconds / 60
hours = minutes / 60
days = hours / 24
weeks = days / 7
months = days / 30
years = days / 365
# Return appropriate string
if seconds < 60:
return "just now"
elif minutes < 60:
m = int(minutes)
return f"{m} minute{'s' if m != 1 else ''} ago"
elif hours < 24:
h = int(hours)
return f"{h} hour{'s' if h != 1 else ''} ago"
elif days < 7:
d = int(days)
return f"{d} day{'s' if d != 1 else ''} ago"
elif weeks < 4:
w = int(weeks)
return f"{w} week{'s' if w != 1 else ''} ago"
elif months < 12:
mo = int(months)
return f"{mo} month{'s' if mo != 1 else ''} ago"
else:
y = int(years)
return f"{y} year{'s' if y != 1 else ''} ago"

View File

@@ -239,6 +239,32 @@ class TestTimeEntryTemplateRoutes:
response = client.get('/templates', follow_redirects=False)
assert response.status_code == 302 # Redirect to login
@pytest.mark.smoke
def test_list_templates_with_usage_data(self, authenticated_client, user, project):
"""Test templates list page renders correctly with templates that have usage data"""
# Create a template with usage data (last_used_at set)
from datetime import datetime, timezone
from app.models import TimeEntryTemplate
from app import db
template = TimeEntryTemplate(
user_id=user.id,
name='Used Template',
project_id=project.id,
default_duration_minutes=60,
usage_count=5,
last_used_at=datetime.now(timezone.utc)
)
db.session.add(template)
db.session.commit()
# Access the list page
response = authenticated_client.get('/templates')
assert response.status_code == 200
assert b'Used Template' in response.data
# Verify that timeago filter is working (should show "just now" or similar)
assert b'ago' in response.data or b'just now' in response.data
def test_create_template_page_get(self, authenticated_client):
"""Test accessing create template page"""
response = authenticated_client.get('/templates/create')

View File

@@ -266,6 +266,169 @@ def test_format_money_filter_invalid(app):
assert result == "not a number"
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_none(app):
"""Test timeago filter with None."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
result = filter_func(None)
assert result == ""
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_just_now(app):
"""Test timeago filter with very recent datetime."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
# Create datetime for 30 seconds ago
now = datetime.datetime.now(datetime.timezone.utc)
dt = now - datetime.timedelta(seconds=30)
result = filter_func(dt)
assert result == "just now"
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_minutes(app):
"""Test timeago filter with minutes ago."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
# Create datetime for 5 minutes ago
now = datetime.datetime.now(datetime.timezone.utc)
dt = now - datetime.timedelta(minutes=5)
result = filter_func(dt)
assert "minute" in result
assert "ago" in result
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_hours(app):
"""Test timeago filter with hours ago."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
# Create datetime for 3 hours ago
now = datetime.datetime.now(datetime.timezone.utc)
dt = now - datetime.timedelta(hours=3)
result = filter_func(dt)
assert "hour" in result
assert "ago" in result
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_days(app):
"""Test timeago filter with days ago."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
# Create datetime for 2 days ago
now = datetime.datetime.now(datetime.timezone.utc)
dt = now - datetime.timedelta(days=2)
result = filter_func(dt)
assert "day" in result
assert "ago" in result
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_weeks(app):
"""Test timeago filter with weeks ago."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
# Create datetime for 2 weeks ago
now = datetime.datetime.now(datetime.timezone.utc)
dt = now - datetime.timedelta(weeks=2)
result = filter_func(dt)
assert "week" in result
assert "ago" in result
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_months(app):
"""Test timeago filter with months ago."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
# Create datetime for 60 days ago (2 months)
now = datetime.datetime.now(datetime.timezone.utc)
dt = now - datetime.timedelta(days=60)
result = filter_func(dt)
assert "month" in result
assert "ago" in result
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_years(app):
"""Test timeago filter with years ago."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
# Create datetime for 400 days ago (over a year)
now = datetime.datetime.now(datetime.timezone.utc)
dt = now - datetime.timedelta(days=400)
result = filter_func(dt)
assert "year" in result
assert "ago" in result
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_future(app):
"""Test timeago filter with future datetime."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
# Create datetime in the future
now = datetime.datetime.now(datetime.timezone.utc)
dt = now + datetime.timedelta(hours=2)
result = filter_func(dt)
assert result == "just now"
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_naive_datetime(app):
"""Test timeago filter with naive datetime (no timezone)."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
# Create naive datetime for 1 hour ago
dt = datetime.datetime.now() - datetime.timedelta(hours=1)
result = filter_func(dt)
# Should still work and convert to UTC
assert "ago" in result or result == "just now"
@pytest.mark.unit
@pytest.mark.utils
def test_timeago_filter_singular_plural(app):
"""Test timeago filter uses correct singular/plural forms."""
register_template_filters(app)
with app.app_context():
filter_func = app.jinja_env.filters.get('timeago')
now = datetime.datetime.now(datetime.timezone.utc)
# Test singular (1 minute)
dt = now - datetime.timedelta(minutes=1)
result = filter_func(dt)
assert "1 minute ago" in result
# Test plural (2 minutes)
dt = now - datetime.timedelta(minutes=2)
result = filter_func(dt)
assert "2 minutes ago" in result
# ============================================================================
# Context Processor Tests
# ============================================================================