Merge pull request #607 from MacJediWizard/fix/upstream-task-service-status

fix(tasks): service-layer status validator + preview JS now kanban-aware (companion to #606)
This commit is contained in:
Dries Peeters
2026-05-06 06:41:22 +02:00
committed by GitHub
3 changed files with 25 additions and 13 deletions
+14 -2
View File
@@ -43,7 +43,10 @@ class TaskService:
self.task_repo = TaskRepository()
self.project_repo = ProjectRepository()
VALID_STATUSES = ("todo", "in_progress", "review", "done", "cancelled")
# Last-ditch fallback — only used if KanbanColumn lookup fails.
# The real validation happens via KanbanColumn.get_valid_status_keys(project_id=...)
# so users with custom kanban columns (e.g. on_hold) aren't silently coerced to todo.
VALID_STATUSES = ("todo", "in_progress", "review", "done", "on_hold", "cancelled")
def create_task(
self,
@@ -82,7 +85,16 @@ class TaskService:
if not project:
return {"success": False, "message": "Invalid project", "error": "invalid_project"}
task_status = status if status and status in self.VALID_STATUSES else TaskStatus.TODO.value
# Validate status against the configured kanban columns for this project.
# Falls back to the hardcoded VALID_STATUSES tuple if KanbanColumn is unavailable
# (e.g. table not yet seeded during a fresh migration).
try:
from app.models import KanbanColumn
allowed = set(KanbanColumn.get_valid_status_keys(project_id=project_id) or self.VALID_STATUSES)
except Exception:
allowed = set(self.VALID_STATUSES)
task_status = status if status and status in allowed else TaskStatus.TODO.value
# Create task
task = self.task_repo.create(
+8 -6
View File
@@ -81,7 +81,11 @@
</span>
<span id="statusPreview" class="status-badge status-{{ request.form.get('status') or request.args.get('status') or 'todo' }}">
{% set status_preview = request.form.get('status') or request.args.get('status') or 'todo' %}
{% if status_preview == 'in_progress' %}{{ _('In Progress') }}{% elif status_preview == 'review' %}{{ _('Review') }}{% elif status_preview == 'done' %}{{ _('Done') }}{% elif status_preview == 'cancelled' %}{{ _('Cancelled') }}{% else %}{{ _('To Do') }}{% endif %}
{%- set _matched = namespace(label=None) -%}
{%- for col in kanban_columns -%}
{%- if col.key == status_preview -%}{%- set _matched.label = col.label -%}{%- endif -%}
{%- endfor -%}
{{ _(_matched.label) if _matched.label else _('To Do') }}
</span>
</div>
@@ -470,11 +474,9 @@ document.addEventListener('DOMContentLoaded', function() {
high: { text: '{{ _('High') }}', class: 'priority-high' },
urgent: { text: '{{ _('Urgent') }}', class: 'priority-urgent' },
} : {
todo: { text: '{{ _('To Do') }}', class: 'status-todo' },
in_progress: { text: '{{ _('In Progress') }}', class: 'status-in_progress' },
review: { text: '{{ _('Review') }}', class: 'status-review' },
done: { text: '{{ _('Done') }}', class: 'status-done' },
cancelled: { text: '{{ _('Cancelled') }}', class: 'status-cancelled' },
{%- for col in kanban_columns %}
{{ col.key }}: { text: '{{ _(col.label) }}', class: 'status-{{ col.key }}' },
{%- endfor %}
};
const def = map[value] || Object.values(map)[0];
// Reset classes preserving base class
+3 -5
View File
@@ -650,11 +650,9 @@ document.addEventListener('DOMContentLoaded', function() {
high: { text: '{{ _('High') }}', class: 'priority-high' },
urgent: { text: '{{ _('Urgent') }}', class: 'priority-urgent' },
} : {
todo: { text: '{{ _('To Do') }}', class: 'status-todo' },
in_progress: { text: '{{ _('In Progress') }}', class: 'status-in_progress' },
review: { text: '{{ _('Review') }}', class: 'status-review' },
done: { text: '{{ _('Done') }}', class: 'status-done' },
cancelled: { text: '{{ _('Cancelled') }}', class: 'status-cancelled' },
{%- for col in kanban_columns %}
{{ col.key }}: { text: '{{ _(col.label) }}', class: 'status-{{ col.key }}' },
{%- endfor %}
};
const def = map[value] || Object.values(map)[0];
element.className = element.className.split(' ').filter(c => !c.startsWith(type === 'priority' ? 'priority-' : 'status-')).join(' ').trim();