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.
218 lines
7.3 KiB
Python
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})
|
|
)
|