mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-08 04:30:20 -06:00
Merge branch 'develop' into 152-getting-started-doc-first-login
This commit is contained in:
@@ -104,48 +104,60 @@ def register_template_filters(app):
|
||||
return f"{float(value):,.2f}"
|
||||
except Exception:
|
||||
return str(value)
|
||||
|
||||
def get_logo_base64(logo_path):
|
||||
"""Convert logo file to base64 data URI for PDF embedding"""
|
||||
import os
|
||||
import base64
|
||||
import mimetypes
|
||||
|
||||
@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 ""
|
||||
|
||||
if not logo_path:
|
||||
print("DEBUG: logo_path is None or empty")
|
||||
return None
|
||||
|
||||
if not os.path.exists(logo_path):
|
||||
print(f"DEBUG: Logo file does not exist: {logo_path}")
|
||||
return None
|
||||
# Import here to avoid circular imports
|
||||
from datetime import datetime, timezone
|
||||
|
||||
try:
|
||||
print(f"DEBUG: Reading logo from: {logo_path}")
|
||||
with open(logo_path, 'rb') as logo_file:
|
||||
logo_data = base64.b64encode(logo_file.read()).decode('utf-8')
|
||||
|
||||
# Detect MIME type
|
||||
mime_type, _ = mimetypes.guess_type(logo_path)
|
||||
if not mime_type:
|
||||
# Try to detect from file extension
|
||||
ext = os.path.splitext(logo_path)[1].lower()
|
||||
mime_map = {
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.webp': 'image/webp'
|
||||
}
|
||||
mime_type = mime_map.get(ext, 'image/png')
|
||||
|
||||
print(f"DEBUG: Logo encoded successfully, MIME type: {mime_type}, size: {len(logo_data)} bytes")
|
||||
return f'data:{mime_type};base64,{logo_data}'
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Error encoding logo: {e}")
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
return None
|
||||
|
||||
# Make get_logo_base64 available in templates as a global function
|
||||
app.jinja_env.globals.update(get_logo_base64=get_logo_base64)
|
||||
# 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"
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user