delete app tasks

This commit is contained in:
2023-11-13 08:52:18 +00:00
parent 0a3e343adb
commit 13f7080503
28 changed files with 3 additions and 1381 deletions

View File

@@ -56,7 +56,6 @@ INSTALLED_APPS = [
"posts.apps.PostsConfig", "posts.apps.PostsConfig",
"members.apps.MembersConfig", "members.apps.MembersConfig",
"blackboard.apps.BlackboardConfig", "blackboard.apps.BlackboardConfig",
"tasks.apps.TasksConfig",
"gallery.apps.GalleryConfig", "gallery.apps.GalleryConfig",
"intern.apps.InternConfig", "intern.apps.InternConfig",
"finance.apps.FinanceConfig", "finance.apps.FinanceConfig",

View File

@@ -39,7 +39,6 @@ urlpatterns = [
path("jobs/", include("blackboard.urls")), path("jobs/", include("blackboard.urls")),
path("posts/", include("posts.urls")), path("posts/", include("posts.urls")),
path("search/", include("search.urls")), path("search/", include("search.urls")),
path("tasks/", include("tasks.urls")),
path( path(
"discord/", "discord/",
RedirectView.as_view(url="https://discord.com/invite/7qRuuMA"), RedirectView.as_view(url="https://discord.com/invite/7qRuuMA"),

View File

@@ -3,8 +3,6 @@ from ckeditor_uploader.widgets import CKEditorUploadingWidget
from django import forms from django import forms
from django.forms.widgets import HiddenInput from django.forms.widgets import HiddenInput
from tasks.models import Task, TaskList
from .models import Attachment, Etherpad, FileUpload, Topic, TopicGroup from .models import Attachment, Etherpad, FileUpload, Topic, TopicGroup
@@ -33,7 +31,6 @@ class TopicAdminForm(forms.ModelForm):
labels = { labels = {
"title": "Titel", "title": "Titel",
"slug": "Permalink", "slug": "Permalink",
"task_list": "Aufgabenbereich",
"description": "Beschreibung", "description": "Beschreibung",
} }

View File

@@ -1,4 +1,4 @@
# Generated by Django 4.1.2 on 2022-12-21 11:42 # Generated by Django 4.2.6 on 2023-11-08 11:32
import datetime import datetime
from django.db import migrations, models from django.db import migrations, models
@@ -7,12 +7,9 @@ import fet2020.utils
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = []
("tasks", "0004_set_fields_unique"),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
@@ -83,15 +80,6 @@ class Migration(migrations.Migration):
("slug", models.SlugField()), ("slug", models.SlugField()),
("archive", models.BooleanField(default=False, verbose_name="Archiv")), ("archive", models.BooleanField(default=False, verbose_name="Archiv")),
("description", models.TextField(blank=True, null=True)), ("description", models.TextField(blank=True, null=True)),
(
"task_list",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="tasks.tasklist",
),
),
( (
"topic_group", "topic_group",
models.ForeignKey( models.ForeignKey(

View File

@@ -12,7 +12,6 @@ from django.utils.text import slugify
from documents import create_pad, get_pad_html from documents import create_pad, get_pad_html
from documents.api import get_pad_link from documents.api import get_pad_link
from fet2020.utils import create_random_id from fet2020.utils import create_random_id
from tasks.models import TaskList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -58,9 +57,6 @@ class Topic(models.Model):
topic_group = models.ForeignKey( topic_group = models.ForeignKey(
TopicGroup, on_delete=models.CASCADE, verbose_name="Themenbereich" TopicGroup, on_delete=models.CASCADE, verbose_name="Themenbereich"
) )
task_list = models.ForeignKey(
TaskList, blank=True, on_delete=models.CASCADE, null=True
)
objects = models.Manager() objects = models.Manager()

View File

@@ -7,7 +7,6 @@ from .views import (
AttachmentUpdateView, AttachmentUpdateView,
EtherpadCreateView, EtherpadCreateView,
FileUploadCreateView, FileUploadCreateView,
TaskCreateView,
TopicCreateView, TopicCreateView,
TopicDetailView, TopicDetailView,
TopicUpdateView, TopicUpdateView,
@@ -50,7 +49,6 @@ topic_urlpatterns = [
AttachmentCreateView.as_view(), AttachmentCreateView.as_view(),
name="attachment_create", name="attachment_create",
), ),
path("create-task/", TaskCreateView.as_view(), name="task_create"),
] ]
urlpatterns = [ urlpatterns = [

View File

@@ -11,8 +11,6 @@ from django.views.generic.edit import CreateView, UpdateView
from authentications.decorators import authenticated_user from authentications.decorators import authenticated_user
from documents.etherpadlib import add_ep_cookie from documents.etherpadlib import add_ep_cookie
from fet2020.utils import add_log_action from fet2020.utils import add_log_action
from tasks.forms import InternTaskCreateForm
from tasks.models import Task
from .forms import ( from .forms import (
AttachmentCreateForm, AttachmentCreateForm,
@@ -70,21 +68,8 @@ class TopicDetailView(LoginRequiredMixin, DetailView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
attachments = Attachment.objects.filter(topic=self.object).order_by("title") attachments = Attachment.objects.filter(topic=self.object).order_by("title")
tasks = None
if self.object.task_list:
tasks = deque(
Task.taskmanager.get_tasks(
user=None,
assigned_tasks=True,
task_list=self.object.task_list,
completed=False,
)
)
context["topic"] = self.object
context["attachments"] = attachments context["attachments"] = attachments
context["tasks"] = tasks context["topic"] = self.object
return context return context
@@ -238,24 +223,3 @@ class FileUploadCreateView(LoginRequiredMixin, CreateView):
def get_success_url(self): def get_success_url(self):
return reverse("intern:attachment", kwargs=self.kwargs) return reverse("intern:attachment", kwargs=self.kwargs)
class TaskCreateView(LoginRequiredMixin, CreateView):
form_class = InternTaskCreateForm
model = Task
template_name = "intern/task_create.html"
def form_valid(self, form):
form.instance.created_by = self.request.user
add_log_action(self.request, form, "tasks", "task", True)
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
slug = self.kwargs.get("slug")
topic = Topic.objects.get(slug=slug)
context["topic"] = topic
return context
def get_success_url(self):
return reverse("intern:topic", kwargs=self.kwargs)

View File

@@ -1,127 +0,0 @@
from django.contrib import admin
from .forms import DocumentInlineForm, TaskAdminForm, TaskListAdminForm
from .models import Document, Task, TaskList
class DocumentInline(admin.TabularInline):
form = DocumentInlineForm
model = Document
extra = 0
verbose_name = "Dokument"
verbose_name_plural = "Do­ku­men­ten­samm­lung"
class TaskListAdmin(admin.ModelAdmin):
form = TaskListAdminForm
model = TaskList
fieldsets = (
(
None,
{
"fields": (
"name",
"shortterm",
"slug",
"users",
)
},
),
)
readonly_fields = ("slug",)
def add_view(self, request, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
return super().add_view(
request,
form_url,
extra_context=extra_context,
)
def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
return super().change_view(
request,
object_id,
form_url,
extra_context=extra_context,
)
class TaskAdmin(admin.ModelAdmin):
form = TaskAdminForm
model = Task
inlines = (DocumentInline,)
fieldsets = (
(
None,
{
"fields": (
"title",
"task_list",
"note",
"priority",
),
},
),
(
"Fälligkeit",
{
"fields": (
"due_date",
"assigned_to",
),
},
),
(
"Abgeschlossen",
{
"fields": (
"completed_date",
"completed",
),
},
),
)
list_display = [
"title",
"task_list",
"assigned_to",
"due_date",
"completed",
"priority",
]
list_filter = ("task_list",)
search_fields = ("title",)
def add_view(self, request, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
return super().add_view(
request,
form_url,
extra_context=extra_context,
)
def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
return super().change_view(
request,
object_id,
form_url,
extra_context=extra_context,
)
def save_model(self, request, obj, form, change):
obj.created_by = request.user
super().save_model(request, obj, form, change)
admin.site.register(TaskList, TaskListAdmin)
admin.site.register(Task, TaskAdmin)

View File

@@ -1,11 +0,0 @@
from django.apps import AppConfig
from django.db.models.signals import post_migrate
from fet2020.utils import create_perms
class TasksConfig(AppConfig):
name = "tasks"
def ready(self):
post_migrate.connect(create_perms, sender=self)

View File

@@ -1,221 +0,0 @@
from ckeditor.widgets import CKEditorWidget
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import User
from django.core.validators import ValidationError
from django.forms.widgets import HiddenInput
from django.utils import timezone
from .models import Document, Task, TaskList
class DateInput(forms.DateInput):
input_type = "date"
class DocumentInlineForm(forms.ModelForm):
class Meta:
model = Document
fields = [
"title",
"date",
]
labels = {
"title": "Titel",
}
class TaskListAdminForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
label="Benutzer",
help_text="Es können nur die Benutzer ausgewählt werden, die sich auf der Homepage angemeldet haben.",
queryset=User.objects.all().order_by("username"),
widget=FilteredSelectMultiple("User", is_stacked=False),
)
class Meta:
model = TaskList
fields = "__all__"
labels = {
"name": "Titel",
"shortterm": "Kürzel für den Link",
"slug": "Permalink",
}
class TaskAdminForm(forms.ModelForm):
class Meta:
model = Task
fields = "__all__"
labels = {
"title": "Titel",
"shortterm": "Kürzel für den Link",
"slug": "Permalink",
"task_list": "Aufgabenbereich",
"due_date": "Fälligkeit",
"completed": "Abgeschlossen",
"completed_date": "Datum der Fertigstellung",
"assigned_to": "Zuweisen an",
"note": "Notizen",
"priority": "Priorität",
}
widgets = {
"note": CKEditorWidget(config_name="default"),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # to get the self.fields set
self.fields["assigned_to"].empty_label = "Alle"
self.fields["assigned_to"].queryset = self.fields[
"assigned_to"
].queryset.order_by("username")
class TaskCreateForm(forms.ModelForm):
class Meta:
model = Task
fields = [
"title",
"task_list",
"due_date",
"assigned_to",
"note",
]
labels = {
"title": "Titel des Tasks",
"task_list": "Task-Gruppe",
"assigned_to": "Zuweisen an",
"due_date": "Fälligkeitsdatum",
"note": "Notizen",
}
widgets = {
"due_date": DateInput(format=("%Y-%m-%d")),
"note": CKEditorWidget(config_name="intern"),
}
def __init__(self, *args, **kwargs):
if "user" in kwargs:
user = kwargs.pop("user")
else:
user = None
super().__init__(*args, **kwargs) # to get the self.fields set
self.fields["assigned_to"].empty_label = "Alle"
qs = self.fields["assigned_to"].queryset
self.fields["assigned_to"].queryset = qs.order_by("username")
if user:
self.fields["task_list"].queryset = TaskList.objects.filter(users=user)
def clean(self):
cleaned_data = super().clean()
assigned_to = cleaned_data["assigned_to"]
if assigned_to:
task_list = TaskList.objects.get(id=cleaned_data["task_list"].id)
if not task_list.users.filter(username=assigned_to.username):
raise ValidationError(
_(
f"User '{assigned_to}' gibt es nicht in der User-Liste der Task-Gruppe '{task_list}'."
)
)
return cleaned_data
class TaskUpdateForm(forms.ModelForm):
class Meta:
model = Task
fields = [
"assigned_to",
"due_date",
"completed",
"completed_date",
"note",
"task_list",
]
labels = {
"assigned_to": "Zuweisen an",
"due_date": "Fälligkeitsdatum",
"completed": "Abgeschlossen",
"completed_date": "Datum der Fertigstellung",
"note": "Notizen",
}
widgets = {
"due_date": DateInput(
format=("%Y-%m-%d"),
),
"note": CKEditorWidget(config_name="intern"),
"task_list": HiddenInput,
}
def __init__(self, *args, **kwargs):
if "task_list" in kwargs:
task_list = kwargs.pop("task_list")
else:
task_list = None
super().__init__(*args, **kwargs) # to get the self.fields set
self.fields["assigned_to"].empty_label = "Alle"
if task_list:
qs = TaskList.objects.get(id=task_list.id).users
self.fields["assigned_to"].queryset = qs.order_by("username")
class DocumentCreateForm(forms.ModelForm):
class Meta:
model = Document
fields = [
"title",
"date",
"task",
]
labels = {
"title": "Titel",
"date": "Datum",
}
widgets = {
"date": DateInput(format=("%d-%m-%Y")),
"task": HiddenInput,
}
class InternTaskCreateForm(TaskCreateForm):
# form for creating a task from intern page.
class Meta:
model = Task
fields = [
"title",
"task_list",
"due_date",
"assigned_to",
"note",
]
labels = {
"title": "Titel des Tasks",
"task_list": "Task-Gruppe",
"due_date": "Fälligkeitsdatum",
"assigned_to": "Zuweisen an",
}
widgets = {
"due_date": DateInput(
format=("%d-%m-%Y"),
),
"task_list": HiddenInput,
}

View File

@@ -1,20 +0,0 @@
from django.db import models
from django.db.models import Q
class TaskManager(models.Manager):
def get_tasks(self, user, assigned_tasks, task_list, completed):
# None ... assigned to all users
qs_all = self.get_queryset()
qs = qs_all.filter(assigned_to__id=user)
if not assigned_tasks:
qs_tmp = qs_all.filter(
Q(assigned_to=None) & Q(task_list__users__id__exact=user)
)
qs = (qs | qs_tmp).distinct()
if task_list:
qs = qs.filter(task_list=task_list)
return qs.filter(completed=completed)

View File

@@ -1,50 +0,0 @@
# Generated by Django 3.1.5 on 2021-01-29 18:35
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='TaskList',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=60)),
('slug', models.SlugField(blank=True, null=True, unique=True)),
('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Auf\xadga\xadben\xadbe\xadreich',
'verbose_name_plural': 'Auf\xadga\xadben\xadbe\xadreiche',
},
),
migrations.CreateModel(
name='Task',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=140, verbose_name='Titel')),
('created_date', models.DateTimeField(auto_now_add=True)),
('due_date', models.DateField(blank=True, null=True, verbose_name='Fälligkeit')),
('completed', models.BooleanField(default=False, verbose_name='Abgeschlossen')),
('completed_date', models.DateField(blank=True, null=True)),
('note', models.TextField(blank=True, null=True, verbose_name='Notizen')),
('priority', models.PositiveIntegerField(blank=True, null=True, verbose_name='Priorität')),
('assigned_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assigned_to', to=settings.AUTH_USER_MODEL, verbose_name='Zugewiesen an')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_by', to=settings.AUTH_USER_MODEL)),
('task_list', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='tasks.tasklist', verbose_name='Aufgabenbereich')),
],
options={
'verbose_name': 'Aufgabe',
'verbose_name_plural': 'Aufgaben',
},
),
]

View File

@@ -1,103 +0,0 @@
# Generated by Django 4.1.2 on 2022-12-21 11:42
import datetime
from django.db import migrations, models
import django.db.models.deletion
import fet2020.utils
class Migration(migrations.Migration):
dependencies = [
("tasks", "0001_initial"),
]
operations = [
migrations.AlterModelOptions(
name="task",
options={
"ordering": (
"task_list",
models.OrderBy(
models.F("due_date"), descending=True, nulls_first=True
),
),
"verbose_name": "Aufgabe",
"verbose_name_plural": "Aufgaben",
},
),
migrations.AddField(
model_name="task",
name="slug",
field=models.SlugField(blank=True, null=True),
),
migrations.AddField(
model_name="task",
name="slug_id",
field=models.CharField(
default=fet2020.utils.create_random_id,
editable=False,
max_length=8,
null=True,
),
),
migrations.AddField(
model_name="tasklist",
name="shortterm",
field=models.CharField(blank=True, max_length=128, null=True),
),
migrations.AlterField(
model_name="task",
name="title",
field=models.CharField(max_length=128, verbose_name="Titel"),
),
migrations.AlterField(
model_name="tasklist",
name="name",
field=models.CharField(max_length=128),
),
migrations.AlterField(
model_name="tasklist",
name="slug",
field=models.SlugField(blank=True, null=True),
),
migrations.CreateModel(
name="Document",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=128, verbose_name="Titel")),
(
"slug_id",
models.CharField(
default=fet2020.utils.create_random_id,
editable=False,
max_length=8,
unique=True,
),
),
("etherpad_key", models.CharField(blank=True, max_length=50)),
(
"date",
models.DateField(default=datetime.date.today, verbose_name="Datum"),
),
(
"task",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="tasks.task"
),
),
],
options={
"verbose_name": "Dokument",
"verbose_name_plural": "Dokumente",
},
),
]

View File

@@ -1,30 +0,0 @@
# Generated by Django 4.1.2 on 2022-12-21 11:43
from django.db import migrations
import fet2020.utils
from django.utils.text import slugify
def forwards_func(apps, schema_editor):
Tasks = apps.get_model("tasks", "Task")
for elem in Tasks.objects.all():
elem.slug_id = fet2020.utils.create_random_id()
elem.slug = elem.slug_id + "-" + slugify(elem.title)
elem.save(update_fields=["slug_id", "slug"])
TaskLists = apps.get_model("tasks", "TaskList")
for elem in TaskLists.objects.all():
elem.shortterm = slugify(elem.name)
elem.slug = slugify(elem.shortterm)
elem.save(update_fields=["shortterm", "slug"])
class Migration(migrations.Migration):
dependencies = [
("tasks", "0002_alter_task_options_task_slug_task_slug_id_and_more"),
]
operations = [
migrations.RunPython(forwards_func, reverse_code=migrations.RunPython.noop),
]

View File

@@ -1,39 +0,0 @@
# Generated by Django 4.1.2 on 2022-12-21 11:43
from django.db import migrations, models
import fet2020.utils
class Migration(migrations.Migration):
dependencies = [
("tasks", "0003_populate_unique_values"),
]
operations = [
migrations.AlterField(
model_name="task",
name="slug",
field=models.SlugField(blank=True, unique=True),
),
migrations.AlterField(
model_name="task",
name="slug_id",
field=models.CharField(
default=fet2020.utils.create_random_id,
editable=False,
max_length=8,
unique=True,
),
),
migrations.AlterField(
model_name="tasklist",
name="shortterm",
field=models.CharField(blank=True, max_length=128, unique=True),
),
migrations.AlterField(
model_name="tasklist",
name="slug",
field=models.SlugField(blank=True, unique=True),
),
]

View File

@@ -1,145 +0,0 @@
from datetime import date
from django.conf import settings
from django.contrib.auth.models import User
from django.core.validators import ValidationError
from django.db import models
from django.db.models import F
from django.db.models.constraints import UniqueConstraint
from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify
from documents import create_pad
from documents.api import get_pad_link
from fet2020.utils import create_random_id
from .managers import TaskManager
class TaskList(models.Model):
name = models.CharField(max_length=128)
shortterm = models.CharField(max_length=128, unique=True, blank=True)
slug = models.SlugField(unique=True, blank=True)
users = models.ManyToManyField(User, blank=True)
objects = models.Manager()
class Meta:
verbose_name = "Auf­ga­ben­be­reich"
verbose_name_plural = "Auf­ga­ben­be­reiche"
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("tasks:index")
def clean(self):
if not self.shortterm:
self.shortterm = slugify(self.name)
self.slug = slugify(self.shortterm)
class Task(models.Model):
title = models.CharField(verbose_name="Titel", max_length=128)
slug_id = models.CharField(
default=create_random_id, max_length=8, unique=True, editable=False
)
slug = models.SlugField(unique=True, blank=True)
task_list = models.ForeignKey(
TaskList, verbose_name="Aufgabenbereich", on_delete=models.CASCADE, null=True
)
created_date = models.DateTimeField(auto_now_add=True)
due_date = models.DateField(verbose_name="Fälligkeit", blank=True, null=True)
completed = models.BooleanField(verbose_name="Abgeschlossen", default=False)
completed_date = models.DateField(blank=True, null=True)
created_by = models.ForeignKey(
User,
related_name="created_by",
on_delete=models.CASCADE,
)
assigned_to = models.ForeignKey(
User,
blank=True,
null=True,
related_name="assigned_to",
on_delete=models.CASCADE,
verbose_name="Zugewiesen an",
)
note = models.TextField(verbose_name="Notizen", blank=True, null=True)
priority = models.PositiveIntegerField(
verbose_name="Priorität", blank=True, null=True
)
objects = models.Manager()
taskmanager = TaskManager()
class Meta:
ordering = ("task_list", F("due_date").desc(nulls_first=True))
verbose_name = "Aufgabe"
verbose_name_plural = "Aufgaben"
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("tasks:task", kwargs={"slug": self.slug})
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self.slug_id + "-" + slugify(self.title)
if self.completed and not self.completed_date:
self.completed_date = timezone.now().date()
if not self.completed and self.completed_date:
self.completed_date = None
super().save(*args, **kwargs)
class Document(models.Model):
title = models.CharField(verbose_name="Titel", max_length=128)
slug_id = models.CharField(
default=create_random_id, max_length=8, unique=True, editable=False
)
etherpad_key = models.CharField(max_length=50, blank=True)
date = models.DateField(verbose_name="Datum", default=date.today)
task = models.ForeignKey(Task, on_delete=models.CASCADE)
objects = models.Manager()
class Meta:
verbose_name = "Dokument"
verbose_name_plural = "Dokumente"
def __str__(self):
return self.title
def get_absolute_url(self):
return get_pad_link(self.etherpad_key)
def clean(self):
pad_name = slugify(str(self.slug_id) + "-" + self.title[:40])
if len(pad_name) > 50:
raise ValidationError(
f"Name zum Erstellen des Etherpads ist zu lange - max. 50 Zeichen. (Länge: {len(pad_name)}, Name: {pad_name})"
)
self.etherpad_key = create_pad(pad_name)
if not self.etherpad_key:
raise ValidationError(
f"Etherpad '{pad_name}' konnte nicht erstellt werden."
)

View File

View File

@@ -1,16 +0,0 @@
from django.urls import path
from . import apps, views
from .views import DocumentCreateView, TaskCreateView, TaskDetailView, TaskUpdateView
app_name = apps.TasksConfig.name
urlpatterns = [
path("", views.index, name="index"),
path("create-task/", TaskCreateView.as_view(), name="task_create"),
path("<slug:slug>/", TaskDetailView.as_view(), name="task"),
path("<slug:slug>/update/", TaskUpdateView.as_view(), name="task_update"),
path(
"<slug:slug>/create-document/", DocumentCreateView.as_view(), name="docu_create"
),
]

View File

@@ -1,149 +0,0 @@
import logging
from collections import deque
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import render
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView
from authentications.decorators import authenticated_user
from documents.api import get_pad_link
from documents.etherpadlib import add_ep_cookie
from fet2020.utils import add_log_action
from intern.models import Topic
from .forms import DocumentCreateForm, TaskCreateForm, TaskUpdateForm
from .models import Document, Task, TaskList
logger = logging.getLogger(__name__)
@authenticated_user
def index(request):
tasklist = None
show_tasklist = None
assigned_tasks = None
completed = False
state_open = False
state_closed = False
if request.method == "POST":
if "btn_checkbox" in request.POST:
for task_id in request.POST.getlist("checkbox"):
task = Task.objects.get(id=task_id)
if not task.completed:
task.completed = True
task.completed_date = timezone.now().date()
task.save()
if request.method == "GET":
if request.GET.get("tasklist"):
if request.GET.get("tasklist") != "all":
tasklist = TaskList.objects.filter(id=request.GET["tasklist"]).first()
show_tasklist = tasklist.id
if request.GET.get("tasks") == "assigned":
assigned_tasks = True
if request.GET.get("open_tasks"):
completed = False
state_open = True
if request.GET.get("closed_tasks"):
completed = True
state_closed = True
tasks = Task.taskmanager.get_tasks(
user=request.user.id,
assigned_tasks=assigned_tasks,
task_list=tasklist,
completed=completed,
)
context = {
"tasks": tasks,
"show_tasklist": show_tasklist,
"assigned_tasks": assigned_tasks,
"state_open": state_open,
"state_closed": state_closed,
}
return render(request, "tasks/index.html", context)
class TaskCreateView(LoginRequiredMixin, CreateView):
model = Task
success_url = reverse_lazy("tasks:index")
template_name = "tasks/task_create.html"
form_class = TaskCreateForm
def form_valid(self, form):
form.instance.created_by = self.request.user
add_log_action(self.request, form, "tasks", "task", True)
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
class TaskUpdateView(LoginRequiredMixin, UpdateView):
form_class = TaskUpdateForm
model = Task
template_name = "tasks/task_update.html"
def form_valid(self, form):
add_log_action(self.request, form, "tasks", "task", False)
return super().form_valid(form)
class TaskDetailView(LoginRequiredMixin, DetailView):
model = Task
template_name = "tasks/task_detail.html"
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
try:
response = add_ep_cookie(self.request, response)
except Exception as e:
logger.info("Etherpad Server doesn't work. Error: %s", e)
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
docus = Document.objects.filter(task__id=self.object.id).order_by("-date")
context["documents"] = docus
try:
context["topic"] = Topic.objects.get(task_list=self.object.task_list)
except ObjectDoesNotExist:
context["topic"] = None
return context
class DocumentCreateView(LoginRequiredMixin, CreateView):
model = Document
template_name = "tasks/attachment_create.html"
form_class = DocumentCreateForm
def form_valid(self, form):
form.instance.created_by = self.request.user
add_log_action(self.request, form, "tasks", "document", True)
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
slug = self.kwargs.get("slug")
context["task"] = Task.objects.get(slug=slug)
return context
def get_success_url(self):
return reverse("tasks:task", kwargs=self.kwargs)

View File

@@ -161,7 +161,6 @@
x-transition:leave-end="transform origin-top opacity-0 scale-95" x-transition:leave-end="transform origin-top opacity-0 scale-95"
> >
<li class="navInternal"><a href="{% url 'admin:index' %}"><i class="fa-fw fa-solid fa-user-secret mr-2"></i>Admin</a></li> <li class="navInternal"><a href="{% url 'admin:index' %}"><i class="fa-fw fa-solid fa-user-secret mr-2"></i>Admin</a></li>
<li class="navInternal"><a href="{% url 'tasks:index' %}"><i class="fa-fw fa-solid fa-list-check mr-2"></i>Tasks</a></li>
<li class="navInternal"><a href="{% url 'intern:index' %}"><i class="fa-fw fa-solid fa-database mr-2"></i>Intern</a></li> <li class="navInternal"><a href="{% url 'intern:index' %}"><i class="fa-fw fa-solid fa-database mr-2"></i>Intern</a></li>
<li class="navInternal"><a href="https://wiki.fet.at"><i class="fa-fw fa-brands fa-wikipedia-w mr-2"></i></i>Wiki</a></li> <li class="navInternal"><a href="https://wiki.fet.at"><i class="fa-fw fa-brands fa-wikipedia-w mr-2"></i></i>Wiki</a></li>
<li class="navInternal"><a href="https://mail.fet.at"><i class="fa-fw fa-solid fa-inbox mr-2"></i></i>Mail</a></li> <li class="navInternal"><a href="https://mail.fet.at"><i class="fa-fw fa-solid fa-inbox mr-2"></i></i>Mail</a></li>

View File

@@ -19,17 +19,6 @@
</div> </div>
{% endif %} {% endif %}
<!-- TODO: tasks -->
{% if tasks %}
<ul class="flex flex-col gap-1 max-w-fit">
{% for task in tasks %}
<li>
<span class="ml-2">{{ task.title|truncatechars:45 }} <a href="{% url 'tasks:task' task.slug %}" class="inline-block text-proprietary dark:text-proprietary-lighter">Link <i class="fa-solid fa-link"></i></a></span>
</li>
{% endfor %}
</ul>
{% endif %}
<ul class="flex flex-col gap-1 max-w-fit"> <ul class="flex flex-col gap-1 max-w-fit">
{% for attachment in attachments %} {% for attachment in attachments %}
<li><a href="{% url 'intern:attachment' topic.topic_group.slug topic.slug attachment.slug %}" class="py-1 inline-block">{{ attachment.title }}</a></li> <li><a href="{% url 'intern:attachment' topic.topic_group.slug topic.slug attachment.slug %}" class="py-1 inline-block">{{ attachment.title }}</a></li>

View File

@@ -1,47 +0,0 @@
{% extends 'base.html' %}
{% block title %}Etherpad hinzufügen{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1">
<h1 class="page-title">Etherpad hinzufügen</h1>
<div class="w-full h-full flex-1 flex justify-center items-center">
<form action="" method="POST" class="w-full max-w-prose sm:px-28 sm:py-4 grid grid-cols-1 gap-y-3 sm:gap-y-6 text-gray-900">
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
<i class="alert-icon fa-solid fa-check-circle"></i>
<h2 class="alert-title">Fehler:</h2>
<div class="alert-body">{{ form.non_field_errors }}</div>
</div>
{% endif %}
<label class="block">
<span class="text-gray-700 dark:text-gray-200">{{ form.title.label }}</span>
{% if form.title.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.title.errors }}</div>
</div>
{% endif %}
<input type="text" id="id_title" name="title" class="mt-1 block w-full rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50" required>
</label>
<label>
<span class="text-gray-700 dark:text-gray-200">{{ form.date.label }}</span>
{% if form.date.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.date.errors }}</div>
</div>
{% endif %}
<input type="date" id="id_date" name="date" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
</label>
<input type="hidden" name="task" value="{{ task.id }}" id="id_task">
<input type="submit" class="block btn btn-primary" value="Hinzufügen">
</form>
</div>
</main>
{% endblock %}

View File

@@ -1,128 +0,0 @@
{% extends 'base.html' %}
{% block title %}Tasks{% endblock %}
{% block content %}
<!-- Main Content -->
<main x-data="modal" class="container mx-auto w-full px-4 my-8 flex-1">
<h1 class="page-title">Tasks</h1>
<div class="sm:flex sm:flex-row-reverse justify-center">
<aside class="sm:w-2/5 sm:max-w-xs sm:pl-4 lg:pl-8 my-8 sm:my-0">
<div class="z-10 fixed sm:sticky top-0 sm:top-4 lg:top-8 left-0 w-full h-full sm:h-auto bg-black sm:bg-transparent bg-opacity-70 flex sm:block items-center justify-center"
x-show="getShowModal"
x-transition:enter="transition duration-300 ease-out"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition duration-150 ease-in"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
>
<div class="max-w-sm sm:w-full mx-4 sm:mx-0 p-4 rounded bg-white dark:bg-gray-800 sm:shadow-lg sm:dark:border-2 sm:dark:border-gray-700"
@click.outside="toggle"
x-show="getShowModal"
x-transition:enter="transition transform ease-out duration-300"
x-transition:enter-start="scale-110 opacity-0"
x-transition:enter-end="scale-100 opacity-100"
x-transition:leave="transition transform ease-in duration-150"
x-transition:leave-start="scale-100 opacity-100"
x-transition:leave-end="scale-110 opacity-0"
>
<div class="flex justify-between items-center mb-2">
<h2 class="text-gray-800 dark:text-gray-100 sm:section-title sm:section-title-margins sm:w-full">
<span class="mr-1 text-gray-400 sm:hidden">
<i class="fa-solid fa-filter"></i>
</span>
Auswahl einschränken
</h2>
<div class="ml-4 -mr-2 px-2 rounded text-xl text-gray-600 dark:text-gray-400 sm:hidden cursor-pointer" @click="closeModal">
<i class="fa-solid fa-xmark"></i>
</div>
</div>
<form action="" method="post" class="grid grid-cols-1 gap-2 sm:gap-4">
{% csrf_token %}
<label class="block">
<span class="text-gray-700 dark:text-gray-200">Task-Gruppe</span>
<select id="id_tasklist" name="tasklist" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
<option value="all" {% if not show_tasklist %}selected{% endif %}>alle Task-Gruppen</option>
{% regroup tasks by task_list as section_list %}
{% for group in section_list %}
<option value="{{ group.grouper.id }}" {% if show_tasklist == group.grouper.id %}selected{% endif %}>{{ group.grouper.name }}</option>
{% endfor %}
</select>
</label>
<label>
<span class="text-gray-700 dark:text-gray-200">Tasks</span>
<select id="id_task" name="tasks" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
<option value="assigned" {% if assigned_tasks %}selected{% endif %}>dir zugewiesene</option>
<option value="all" {% if not assigned_tasks %}selected{% endif %}>alle</option>
</select>
</label>
<label class="inline-flex items-center">
<input type="checkbox" name="open_tasks" {% if state_open %}checked{% endif %} class="rounded border-gray-300 dark:border-none text-proprietary shadow-sm focus:border-blue-300 focus:ring focus:ring-offset-0 focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
<span class="ml-2 text-gray-700 dark:text-gray-200">Offene Tasks</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" name="closed_tasks" {% if state_closed %}checked{% endif %} class="rounded border-gray-300 dark:border-none text-proprietary shadow-sm focus:border-blue-300 focus:ring focus:ring-offset-0 focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
<span class="ml-2 text-gray-700 dark:text-gray-200">Geschlossene Tasks</span>
</label>
<input type="submit" class="block btn btn-primary" value="Anzeigen">
</form>
</div>
</div>
<button id="modal-trigger-1" class="z-10 trigger fixed bottom-4 right-4 bg-proprietary-darker dark:bg-sky-500 text-blue-50 dark:text-sky-900 shadow-lg text-2xl rounded sm:hidden"
@click="openModal"
x-show="getNotShowModal"
x-transition:enter="transition duration-100 ease-in"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition duration-100 ease-out"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
>
<i class="fa-solid fa-filter p-2"></i>
</button>
</aside>
{% if tasks %}
<form action="" method="post" class="my-8 sm:my-0 sm:w-3/5 xl:w-2/5 flex flex-col gap-2 text-gray-600 dark:text-gray-300">
{% csrf_token %}
{% regroup tasks by task_list as section_list %}
{% for group in section_list %}
<section>
<h2 class="mb-1 text-gray-700 dark:text-gray-200">{{ group.grouper }}</h2>
{% for task in group.list %}
<label class="mb-2 flex justify-between items-start">
<div class="inline-flex items-baseline mr-2">
<input type="checkbox" name="checkbox" value="{{ task.id }}" {% if task.completed %}checked{% endif %} class="rounded border-gray-300 dark:border-none text-proprietary shadow-sm focus:border-blue-300 focus:ring focus:ring-offset-0 focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
<span class="ml-2">{{ task.title|truncatechars:45 }} <a href="{% url 'tasks:task' task.slug %}" class="inline-block text-proprietary dark:text-proprietary-lighter">Link <i class="fa-solid fa-link"></i></a></span>
</div>
{% if task.due_date %}
<div class="inline-flex gap-2 flex-shrink-0">
<span class="px-2 py-0.5 rounded-full text-sm font-medium text-proprietary dark:text-blue-100 bg-blue-200 dark:bg-proprietary-dark"><i class="fa-solid fa-hourglass mr-1"></i>{{ task.due_date|date:"d.m.Y" }}</span>
</div>
{% endif %}
</label>
{% endfor %}
</section>
{% endfor %}
<div class="flex flex-col md:flex-row gap-y-2 md:gap-y-0 md:gap-x-2 lg:justify-end mt-4">
<input type="submit" name="btn_checkbox" value="Tasks abschließen" class="btn btn-success block md:flex-grow lg:flex-grow-0">
<a href="{% url 'tasks:task_create' %}" class="btn btn-primary block md:flex-grow lg:flex-grow-0"><i class="fa-solid fa-plus-square mr-2"></i>Task hinzufügen</a>
</div>
</form>
{% else %}
<section>
<h2 class="mb-1 text-gray-700 dark:text-gray-200">Keine Tasks in dieser Liste für dich.</h2>
<div class="flex flex-col md:flex-row gap-y-2 md:gap-y-0 md:gap-x-2 lg:justify-end mt-4">
<a href="{% url 'tasks:task_create' %}" class="btn btn-primary block md:flex-grow lg:flex-grow-0"><i class="fa-solid fa-plus-square mr-2"></i>Task hinzufügen</a>
</div>
</section>
{% endif %}
</div>
</main>
{% endblock %}

View File

@@ -1,86 +0,0 @@
{% extends 'base.html' %}
{% block title %}Task hinzufügen{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1">
<h1 class="page-title">Task hinzufügen</h1>
<div class="w-full h-full flex-1 flex justify-center items-center">
<form action="" method="POST" class="w-full max-w-xs sm:max-w-prose sm:px-28 sm:py-4 grid grid-cols-1 gap-y-3 sm:gap-y-6 text-gray-900">
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
<i class="alert-icon fa-solid fa-check-circle"></i>
<h2 class="alert-title">Fehler:</h2>
<div class="alert-body">{{ form.non_field_errors }}</div>
</div>
{% endif %}
<label class="block">
<span class="text-gray-700 dark:text-gray-200">{{ form.title.label }}</span>
{% if form.title.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.title.errors }}</div>
</div>
{% endif %}
<input type="text" id="id_title" name="title" class="mt-1 block w-full rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50" required>
</label>
<label>
<span class="text-gray-700 dark:text-gray-200">{{ form.task_list.label }}</span>
{% if form.task_list.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.task_list.errors }}</div>
</div>
{% endif %}
<select id="id_task_list" name="task_list" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50" required>
{% for elem in form.task_list %}
{% if forloop.first %}
<option value="">---------</option>
{% else %}
{{ elem }}
{% endif %}
{% endfor %}
</select>
</label>
<label>
<span class="text-gray-700 dark:text-gray-200">{{ form.due_date.label }}</span>
{% if form.due_date.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.due_date.errors }}</div>
</div>
{% endif %}
<input type="date" id="id_due_date" name="due_date" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
</label>
<label>
<span class="text-gray-700 dark:text-gray-200">{{ form.assigned_to.label }}</span>
{% if form.assigned_to.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.assigned_to.errors }}</div>
</div>
{% endif %}
<select id="id_assigned_to" name="assigned_to" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
{% for elem in form.assigned_to %}
{% if forloop.first %}
<option value="">Alle</option>
{% else %}
{{ elem }}
{% endif %}
{% endfor %}
</select>
</label>
<div class="flex flex-col-reverse sm:flex-row gap-3 justify-end pt-4 sm:pt-0">
<a href="{% url 'admin:tasks_tasklist_add' %}" class="block btn btn-secondary-proprietary">Task-Gruppe hinzufügen</a>
<input type="submit" class="block btn btn-primary" value="Hinzufügen">
</div>
</form>
</div>
</main>
{% endblock %}

View File

@@ -1,46 +0,0 @@
{% extends 'base.html' %}
{% block title %}{{ task.title }}{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1">
<h1 class="page-title">Task-Detailansicht</h1>
<div class="flex flex-col gap-y-4 sm:gap-y-4 max-w-prose mx-auto text-gray-700 dark:text-gray-300">
<aside class="flex gap-2 flex-wrap align-bottom text-sm sm:text-base text-gray-600 dark:text-gray-300">
<a class="underline hover:text-gray-900 dark:hover:text-gray-100" href="{% url 'tasks:index' %}">{{ task.task_list.name }}</a>
<span><i class="fa-solid fa-angle-right"></i></span>
<span class="cursor-default">{{ task.title }}</span>
</aside>
{% if task.note %}
<div class="db-page-content-left">
{{ task.note|safe }}
</div>
{% endif %}
{% if task.due_date %}
<div class="text-right -mt-6">
{{ task.due_date }}
</div>
{% endif %}
<section>
<div class="documentList rounded divide-y divide-gray-300 dark:divide-gray-600">
<a href="{% url 'tasks:docu_create' task.slug %}" class="flex justify-between">
<h3 class="text-gray-800 dark:text-gray-200"><i class="fa-solid fa-plus fa-fw mr-1"></i>Neues Etherpad erstellen</h2>
</a>
{% for document in documents %}
<a href="{{ document.get_absolute_url }}" class="flex justify-between items-center gap-2">
<h3 class="text-gray-800 dark:text-gray-200">{{ document.title }}</h2>
<span class=" text-gray-700 dark:text-gray-300">{{ document.date }}<span class="ml-2 hidden sm:inline-block"><i class="fa-solid fa-angle-right text-gray-400 dark:text-gray-600"></i></span>
</a>
{% endfor %}
</div>
</section>
<a href="{% url 'tasks:task_update' task.slug %}" class="btn btn-primary block place-self-end"><i class="fa-solid fa-pen-to-square mr-2"></i>Task bearbeiten</a>
</div>
</main>
{% endblock %}

View File

@@ -1,89 +0,0 @@
{% extends 'base.html' %}
{% block title %}{{ task.title }} bearbeiten{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1">
<h1 class="page-title">Task '{{ task.title }}' bearbeiten</h1>
<div class="w-full h-full flex-1 flex justify-center items-center">
<form action="" method="POST" class="w-full max-w-prose sm:px-28 sm:py-4 grid grid-cols-1 gap-y-3 sm:gap-y-6 text-gray-900">
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
<i class="alert-icon fa-solid fa-check-circle"></i>
<h2 class="alert-title">Fehler:</h2>
<div class="alert-body">{{ form.non_field_errors }}</div>
</div>
{% endif %}
<label>
<span class="text-gray-700 dark:text-gray-200">{{ form.assigned_to.label }}</span>
{% if form.assigned_to.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.assigned_to.errors }}</div>
</div>
{% endif %}
<select id="id_assigned_to" name="assigned_to" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
{% for elem in form.assigned_to %}
{% if forloop.first %}
<option value="">Alle</option>
{% else %}
{{ elem }}
{% endif %}
{% endfor %}
</select>
</label>
<label>
<span class="text-gray-700 dark:text-gray-200">{{ form.due_date.label }}</span>
{% if form.due_date.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.due_date.errors }}</div>
</div>
{% endif %}
<input type="date" id="id_due_date" name="due_date" value="{{ task.due_date|date:"Y-m-d" }}" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
</label>
<label>
<span class="text-gray-700 dark:text-gray-200">{{ form.completed_date.label }}</span>
{% if form.completed_date.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.completed_date.errors }}</div>
</div>
{% endif %}
<input type="date" id="id_completed_date" name="completed_date" value="{{ task.completed_date|date:"Y-m-d" }}" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
</label>
<label>
<input type="checkbox" id="id_completed" name="completed" value="{{ task.id }}" {% if task.completed %}checked{% endif %} class="rounded border-gray-300 dark:border-none text-proprietary shadow-sm focus:border-blue-300 focus:ring focus:ring-offset-0 focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
<span class="text-gray-700 dark:text-gray-200">{{ form.completed.label }}</span>
{% if form.completed.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.completed.errors }}</div>
</div>
{% endif %}
</label>
<label class="block">
<span class="text-gray-700 dark:text-gray-200">{{ form.note.label }}</span>
{% if form.note.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ form.note.errors }}</div>
</div>
{% endif %}
{{ form.media }}
{{ form.note }}
</label>
<input type="hidden" name="task_list" value="{{ task.task_list.id }}" id="id_task_list">
<div class="flex flex-col-reverse sm:flex-row gap-3 justify-end pt-4 sm:pt-0">
<a href="{% url 'admin:tasks_task_change' task.id %}" class="block btn btn-secondary-proprietary">Task im Admin bearbeiten</a>
<input type="submit" class="block btn btn-primary" value="Speichern">
</div>
</form>
</div>
</main>
{% endblock %}