Add kanban updates and static root
This commit is contained in:
+83
-26
@@ -9,11 +9,12 @@ from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.utils import timezone
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_http_methods, require_POST
|
||||
from .forms import ApiKeyForm, EditItemForm, QuickItemForm, UserPreferenceForm, UserSettingsForm
|
||||
from .models import ApiKey, Attachment, Item, Tag, UserPreference
|
||||
from .forms import ApiKeyForm, EditItemForm, KanbanForm, QuickItemForm, UserPreferenceForm, UserSettingsForm
|
||||
from .models import ApiKey, Attachment, Item, Kanban, Tag, UserPreference
|
||||
|
||||
COMMAND_RE = re.compile(r'/(todo|note|link|journal|public|private)\b', re.I)
|
||||
URL_RE = re.compile(r'https?://\S+')
|
||||
@@ -154,19 +155,8 @@ def index(request):
|
||||
if q:
|
||||
items = items.filter(Q(content__icontains=q) | Q(url__icontains=q) | Q(tags__name__icontains=q)).distinct()
|
||||
prefs, _ = UserPreference.objects.get_or_create(user=request.user)
|
||||
journal_days = []
|
||||
if kind == Item.Kind.JOURNAL:
|
||||
today = date.today()
|
||||
year = int(request.GET.get('year', today.year))
|
||||
month = int(request.GET.get('month', today.month))
|
||||
journal_qs = visible_items(request.user).filter(kind=Item.Kind.JOURNAL, created_at__year=year, created_at__month=month)
|
||||
days_with_entries = set(journal_qs.dates('created_at', 'day'))
|
||||
journal_days = [
|
||||
{'day': d, 'date': date(year, month, d).isoformat(), 'has_entries': date(year, month, d) in days_with_entries}
|
||||
for week in Calendar(firstweekday=0).monthdayscalendar(year, month) for d in week if d
|
||||
]
|
||||
return render(request, 'core/index.html', {
|
||||
'form': QuickItemForm(), 'items': items, 'tags': Tag.objects.all(), 'active_tag': tag, 'active_kind': kind, 'active_status': status, 'active_day': day, 'q': q, 'journal_days': journal_days, 'overview_lines': prefs.overview_lines,
|
||||
'form': QuickItemForm(), 'items': items, 'tags': Tag.objects.all(), 'active_tag': tag, 'active_kind': kind, 'active_status': status, 'active_day': day, 'q': q, 'overview_lines': prefs.overview_lines,
|
||||
})
|
||||
|
||||
|
||||
@@ -185,21 +175,39 @@ def new_item(request):
|
||||
|
||||
@login_required
|
||||
def journal(request):
|
||||
if request.method == 'POST':
|
||||
form = QuickItemForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
raw = form.cleaned_data['content']
|
||||
if not re.search(r'/(todo|note|link)\b', raw, re.I):
|
||||
raw = f'/journal {raw}'
|
||||
kind, visibility, content, url = parse_quick_content(raw)
|
||||
item = Item.objects.create(owner=request.user, kind=kind, visibility=visibility, content=content, url=url)
|
||||
attach_files_and_replace_tokens(item, request.FILES.getlist('files'))
|
||||
item.sync_metadata()
|
||||
if request.headers.get('HX-Request'):
|
||||
return render(request, 'core/_item.html', {'item': item, 'overview_lines': request.user.preferences.overview_lines if hasattr(request.user, 'preferences') else 4})
|
||||
return redirect('journal')
|
||||
|
||||
today = date.today()
|
||||
year = int(request.GET.get('year', today.year))
|
||||
month = int(request.GET.get('month', today.month))
|
||||
entries = visible_items(request.user).filter(kind=Item.Kind.JOURNAL, created_at__year=year, created_at__month=month)
|
||||
by_day = {}
|
||||
for item in entries:
|
||||
by_day.setdefault(item.created_at.day, []).append(item)
|
||||
weeks = []
|
||||
for week in Calendar(firstweekday=0).monthdayscalendar(year, month):
|
||||
weeks.append([(day, by_day.get(day, [])) for day in week])
|
||||
selected_day = request.GET.get('day') or today.isoformat()
|
||||
selected = date.fromisoformat(selected_day)
|
||||
year = int(request.GET.get('year', selected.year))
|
||||
month = int(request.GET.get('month', selected.month))
|
||||
base = visible_items(request.user)
|
||||
journal_entries = base.filter(kind=Item.Kind.JOURNAL, created_at__date=selected)
|
||||
created_entries = base.filter(kind__in=[Item.Kind.NOTE, Item.Kind.LINK], created_at__date=selected)
|
||||
completed_todos = base.filter(kind=Item.Kind.TODO, completed_at__date=selected)
|
||||
items = (journal_entries | created_entries | completed_todos).distinct().order_by('-created_at')
|
||||
activity_dates = set(base.filter(kind__in=[Item.Kind.JOURNAL, Item.Kind.NOTE, Item.Kind.LINK], created_at__year=year, created_at__month=month).dates('created_at', 'day'))
|
||||
activity_dates |= set(base.filter(kind=Item.Kind.TODO, completed_at__year=year, completed_at__month=month).dates('completed_at', 'day'))
|
||||
weeks = [[{'day': d, 'date': date(year, month, d).isoformat() if d else '', 'has_entries': d and date(year, month, d) in activity_dates} for d in week] for week in Calendar(firstweekday=0).monthdayscalendar(year, month)]
|
||||
prev_month, prev_year = (12, year - 1) if month == 1 else (month - 1, year)
|
||||
next_month, next_year = (1, year + 1) if month == 12 else (month + 1, year)
|
||||
prefs, _ = UserPreference.objects.get_or_create(user=request.user)
|
||||
return render(request, 'core/journal.html', {
|
||||
'weeks': weeks, 'year': year, 'month': month,
|
||||
'prev_year': prev_year, 'prev_month': prev_month, 'next_year': next_year, 'next_month': next_month,
|
||||
'form': QuickItemForm(), 'items': items, 'tags': Tag.objects.all(), 'weeks': weeks, 'year': year, 'month': month, 'selected_day': selected_day,
|
||||
'prev_year': prev_year, 'prev_month': prev_month, 'next_year': next_year, 'next_month': next_month, 'overview_lines': prefs.overview_lines,
|
||||
})
|
||||
|
||||
|
||||
@@ -256,7 +264,8 @@ def item_detail(request, pk):
|
||||
def toggle_done(request, pk):
|
||||
item = get_object_or_404(Item, pk=pk, owner=request.user, kind=Item.Kind.TODO)
|
||||
item.is_done = not item.is_done
|
||||
item.save(update_fields=['is_done', 'updated_at'])
|
||||
item.completed_at = timezone.now() if item.is_done else None
|
||||
item.save(update_fields=['is_done', 'completed_at', 'updated_at'])
|
||||
if request.headers.get('HX-Request') and item.is_done and request.GET.get('status') != 'archive':
|
||||
return HttpResponse('')
|
||||
return render(request, 'core/_item.html', {'item': item})
|
||||
@@ -305,6 +314,51 @@ def delete_item(request, pk):
|
||||
return JsonResponse({'ok': True})
|
||||
|
||||
|
||||
@login_required
|
||||
def kanban_list(request):
|
||||
if request.method == 'POST':
|
||||
form = KanbanForm(request.POST)
|
||||
if form.is_valid():
|
||||
kanban = form.save(commit=False)
|
||||
kanban.owner = request.user
|
||||
kanban.save()
|
||||
return redirect('kanban_detail', pk=kanban.pk)
|
||||
kanbans = Kanban.objects.filter(owner=request.user).select_related('tag')
|
||||
stats = []
|
||||
for kanban in kanbans:
|
||||
qs = Item.objects.filter(owner=request.user, kind=Item.Kind.TODO, tags=kanban.tag)
|
||||
stats.append({'kanban': kanban, 'open': qs.filter(is_done=False).count(), 'done': qs.filter(is_done=True).count()})
|
||||
return render(request, 'core/kanban_list.html', {'form': KanbanForm(), 'stats': stats})
|
||||
|
||||
|
||||
@login_required
|
||||
def kanban_detail(request, pk):
|
||||
kanban = get_object_or_404(Kanban.objects.select_related('tag'), pk=pk, owner=request.user)
|
||||
todos = Item.objects.filter(owner=request.user, kind=Item.Kind.TODO, tags=kanban.tag).prefetch_related('tags')
|
||||
return render(request, 'core/kanban_detail.html', {
|
||||
'kanban': kanban,
|
||||
'inbox_items': todos.filter(is_done=False, kanban_status='inbox'),
|
||||
'todo_items': todos.filter(is_done=False, kanban_status='todo'),
|
||||
'progress_items': todos.filter(is_done=False, kanban_status='progress'),
|
||||
'done_items': todos.filter(is_done=True),
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def kanban_move(request, pk, item_pk):
|
||||
kanban = get_object_or_404(Kanban, pk=pk, owner=request.user)
|
||||
item = get_object_or_404(Item, pk=item_pk, owner=request.user, kind=Item.Kind.TODO, tags=kanban.tag)
|
||||
status = request.POST.get('status')
|
||||
if status not in {'inbox', 'todo', 'progress', 'done'}:
|
||||
return JsonResponse({'error': 'invalid status'}, status=400)
|
||||
item.kanban_status = status
|
||||
item.is_done = status == 'done'
|
||||
item.completed_at = timezone.now() if item.is_done else None
|
||||
item.save(update_fields=['kanban_status', 'is_done', 'completed_at', 'updated_at'])
|
||||
return JsonResponse({'ok': True})
|
||||
|
||||
|
||||
@login_required
|
||||
def tag_list(request):
|
||||
return render(request, 'core/_tags.html', {'tags': Tag.objects.all(), 'active_tag': request.GET.get('tag')})
|
||||
@@ -340,6 +394,7 @@ def serialize_item(item):
|
||||
'comment': item.comment,
|
||||
'visibility': item.visibility,
|
||||
'is_done': item.is_done,
|
||||
'completed_at': item.completed_at.isoformat() if item.completed_at else None,
|
||||
'due_at': item.due_at.isoformat() if item.due_at else None,
|
||||
'url': item.url,
|
||||
'tags': [t.name for t in item.tags.all()],
|
||||
@@ -384,6 +439,8 @@ def api_item_detail(request, pk):
|
||||
for field in ['content', 'comment', 'kind', 'visibility', 'is_done', 'due_at', 'url']:
|
||||
if field in data:
|
||||
setattr(item, field, data[field])
|
||||
if 'is_done' in data and item.kind == Item.Kind.TODO:
|
||||
item.completed_at = timezone.now() if item.is_done else None
|
||||
item.save()
|
||||
item.sync_metadata()
|
||||
return JsonResponse(serialize_item(item))
|
||||
|
||||
Reference in New Issue
Block a user