Files
project-managment/apps/kanban/views.py
T
sankalp 3197ef1665 feat(apps): implement priority handling and comment system for kanban cards
New Features:
- Add `Priority` enum and `priority` field to `Card` model.
- Add `TicketComment` model and `TicketCommentCreateView` with inline comment support.
- Introduce templatetag filters `add_class`, `field_type`, and `render_markdown`.

Enhancements:
- Extend `CustomUser.is_admin()` to recognise superusers.
- Populate `KanbanConfig.ready()` to import signals for proper initialization.
- Expand ticket views and URLs to include assignee, priority, and comment handling.

Bug Fixes:
- None.
2026-05-28 00:27:02 +05:45

218 lines
7.3 KiB
Python

from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import models
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse, reverse_lazy
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView, View
from apps.core.permissions import AdminRequiredMixin
from apps.kanban.forms import CardForm
from apps.kanban.models import Board, Card, CardComment, Column
class BoardListView(LoginRequiredMixin, ListView):
model = Board
template_name = "pages/kanban/board_list.html"
context_object_name = "boards"
def get_queryset(self):
user = self.request.user
if user.is_admin():
return Board.objects.all()
return Board.objects.for_user(user)
class BoardDetailView(LoginRequiredMixin, DetailView):
model = Board
template_name = "pages/kanban/board_detail.html"
context_object_name = "board"
def get_queryset(self):
user = self.request.user
if user.is_admin():
return Board.objects.all()
return Board.objects.for_user(user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["columns"] = self.object.columns.prefetch_related("cards").all()
context["users"] = self.get_users()
return context
def get_users(self):
from apps.accounts.models import CustomUser
return CustomUser.objects.filter(is_active=True)
class BoardCreateView(LoginRequiredMixin, CreateView):
model = Board
template_name = "pages/kanban/board_form.html"
fields = ["name", "project"]
def form_valid(self, form):
form.instance.owner = self.request.user
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("board_detail", kwargs={"pk": self.object.pk})
class BoardUpdateView(LoginRequiredMixin, UpdateView):
model = Board
template_name = "pages/kanban/board_form.html"
fields = ["name", "project"]
def get_success_url(self):
return reverse_lazy("board_detail", kwargs={"pk": self.object.pk})
class BoardDeleteView(AdminRequiredMixin, DeleteView):
model = Board
template_name = "pages/kanban/board_confirm_delete.html"
success_url = reverse_lazy("board_list")
class ColumnCreateView(LoginRequiredMixin, CreateView):
model = Column
fields = ["name"]
def form_valid(self, form):
board = get_object_or_404(Board, pk=self.kwargs["board_pk"])
form.instance.board = board
form.instance.order = board.columns.count()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("board_detail", kwargs={"pk": self.kwargs["board_pk"]})
class CardCreateView(LoginRequiredMixin, CreateView):
model = Card
form_class = CardForm
template_name = "pages/kanban/card_form.html"
def dispatch(self, request, *args, **kwargs):
self.board = get_object_or_404(Board, pk=self.kwargs["board_pk"])
self.column = get_object_or_404(Column, pk=self.kwargs["column_pk"])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.column = self.column
form.instance.order = self.column.cards.count()
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["board"] = self.board
context["column"] = self.column
context["is_create"] = True
return context
def get_success_url(self):
return reverse_lazy("board_detail", kwargs={"pk": self.board.pk})
class CardDetailView(LoginRequiredMixin, DetailView):
model = Card
template_name = "pages/kanban/card_detail.html"
context_object_name = "card"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["comments"] = self.object.comments.filter(parent=None).prefetch_related("replies__author", "author")
return context
class CardUpdateView(LoginRequiredMixin, UpdateView):
model = Card
form_class = CardForm
template_name = "pages/kanban/card_form.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["board"] = self.object.column.board
context["column"] = self.object.column
context["is_create"] = False
context["comments"] = self.object.comments.filter(parent=None).prefetch_related("replies__author", "author")
return context
def get_success_url(self):
return reverse_lazy("board_detail", kwargs={"pk": self.object.column.board.pk})
class CardDeleteView(LoginRequiredMixin, View):
def post(self, request, pk):
card = get_object_or_404(Card, pk=pk)
board_pk = card.column.board.pk
card.delete()
if request.htmx:
return HttpResponse(status=200)
return HttpResponseRedirect(reverse("board_detail", kwargs={"pk": board_pk}))
class CardMoveView(LoginRequiredMixin, View):
def post(self, request, pk):
card = get_object_or_404(Card, pk=pk)
column_id = request.POST.get("column_id")
new_order = request.POST.get("order")
new_order = int(new_order) if new_order else None
old_column_id = card.column_id
if column_id and str(column_id) != str(old_column_id):
new_column_id = int(column_id)
old_column = card.column
old_column.cards.filter(order__gt=card.order).update(
order=models.F("order") - 1
)
Card.objects.filter(column_id=new_column_id, order__gte=new_order).exclude(
pk=card.pk
).update(order=models.F("order") + 1)
card.column_id = new_column_id
elif new_order is not None:
current_order = card.order
if new_order > current_order:
Card.objects.filter(
column=card.column,
order__gt=current_order,
order__lte=new_order,
).exclude(pk=card.pk).update(order=models.F("order") - 1)
elif new_order < current_order:
Card.objects.filter(
column=card.column,
order__gte=new_order,
order__lt=current_order,
).exclude(pk=card.pk).update(order=models.F("order") + 1)
if new_order is not None:
card.order = new_order
card.save()
return render(request, "partials/kanban/card.html", {"card": card})
class CardCommentCreateView(LoginRequiredMixin, View):
def post(self, request, pk):
card = get_object_or_404(Card, pk=pk)
body = request.POST.get("body", "").strip()
parent_pk = request.POST.get("parent")
if not body:
return HttpResponse(status=400)
comment = CardComment.objects.create(
card=card,
author=request.user,
body=body,
parent_id=parent_pk if parent_pk else None,
)
if request.htmx:
return render(request, "partials/kanban/comment.html", {"comment": comment})
return HttpResponseRedirect(
reverse("card_detail", kwargs={"pk": card.pk})
)