Add simpletodo htmx example app
This commit is contained in:
parent
15bea892e4
commit
db291b50c0
|
|
@ -82,6 +82,7 @@ THIRD_PARTY_APPS = [
|
||||||
"rest_framework.authtoken",
|
"rest_framework.authtoken",
|
||||||
"corsheaders",
|
"corsheaders",
|
||||||
"drf_spectacular",
|
"drf_spectacular",
|
||||||
|
"django_htmx",
|
||||||
]
|
]
|
||||||
|
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = [
|
||||||
|
|
@ -145,6 +146,7 @@ MIDDLEWARE = [
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.middleware.common.BrokenLinkEmailsMiddleware",
|
"django.middleware.common.BrokenLinkEmailsMiddleware",
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
"django_htmx.middleware.HtmxMiddleware",
|
||||||
"vbv_lernwelt.core.middleware.auth.AuthenticationRequiredMiddleware",
|
"vbv_lernwelt.core.middleware.auth.AuthenticationRequiredMiddleware",
|
||||||
"vbv_lernwelt.core.middleware.security.SecurityRequestResponseLoggingMiddleware",
|
"vbv_lernwelt.core.middleware.security.SecurityRequestResponseLoggingMiddleware",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -11,26 +11,15 @@ from rest_framework.authtoken.views import obtain_auth_token
|
||||||
|
|
||||||
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path("", django_view_authentication_exempt(TemplateView.as_view(template_name="pages/home.html")), name="home"),
|
||||||
"",
|
path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"),
|
||||||
django_view_authentication_exempt(
|
|
||||||
TemplateView.as_view(template_name="pages/home.html")
|
|
||||||
),
|
|
||||||
name="home",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
|
|
||||||
),
|
|
||||||
# Django Admin, use {% url 'admin:index' %}
|
# Django Admin, use {% url 'admin:index' %}
|
||||||
path(settings.ADMIN_URL, admin.site.urls),
|
path(settings.ADMIN_URL, admin.site.urls),
|
||||||
# Your stuff: custom urls includes go here
|
# Your stuff: custom urls includes go here
|
||||||
path(
|
path("login/", django_view_authentication_exempt(auth_views.LoginView.as_view(template_name="core/login.html"))),
|
||||||
"login/",
|
path("todo/", include("vbv_lernwelt.simpletodo.urls")),
|
||||||
django_view_authentication_exempt(
|
|
||||||
auth_views.LoginView.as_view(template_name="core/login.html")
|
|
||||||
),
|
|
||||||
),
|
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
# Static file serving when using Gunicorn + Uvicorn for local web socket development
|
# Static file serving when using Gunicorn + Uvicorn for local web socket development
|
||||||
|
|
@ -43,12 +32,9 @@ urlpatterns += [
|
||||||
# DRF auth token
|
# DRF auth token
|
||||||
path("auth-token/", obtain_auth_token),
|
path("auth-token/", obtain_auth_token),
|
||||||
path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"),
|
path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"),
|
||||||
path(
|
path("api/docs/", SpectacularSwaggerView.as_view(url_name="api-schema"), name="api-docs",),
|
||||||
"api/docs/",
|
|
||||||
SpectacularSwaggerView.as_view(url_name="api-schema"),
|
|
||||||
name="api-docs",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
# This allows the error pages to be debugged during development, just visit
|
# This allows the error pages to be debugged during development, just visit
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export VBV_DATABASE_URL='postgres://vbv_lernwelt@localhost:5432/vbv_lernwelt'
|
export VBV_DATABASE_URL='postgres://vbv_lernwelt@localhost:5432/vbv_lernwelt'
|
||||||
#export VBV_DJANGO_LOGGING_CONF=VBV_DJANGO_LOGGING_CONF_CONSOLE_COLOR
|
export VBV_DJANGO_LOGGING_CONF=VBV_DJANGO_LOGGING_CONF_CONSOLE_COLOR
|
||||||
export VBV_DJANGO_DEBUG=True
|
export VBV_DJANGO_DEBUG=True
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ django==3.2.12
|
||||||
# django-cors-headers
|
# django-cors-headers
|
||||||
# django-debug-toolbar
|
# django-debug-toolbar
|
||||||
# django-extensions
|
# django-extensions
|
||||||
|
# django-htmx
|
||||||
# django-model-utils
|
# django-model-utils
|
||||||
# django-redis
|
# django-redis
|
||||||
# django-stubs
|
# django-stubs
|
||||||
|
|
@ -83,6 +84,8 @@ django-debug-toolbar==3.2.4
|
||||||
# via -r requirements-dev.in
|
# via -r requirements-dev.in
|
||||||
django-extensions==3.1.5
|
django-extensions==3.1.5
|
||||||
# via -r requirements-dev.in
|
# via -r requirements-dev.in
|
||||||
|
django-htmx==1.8.0
|
||||||
|
# via -r requirements.in
|
||||||
django-model-utils==4.2.0
|
django-model-utils==4.2.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-redis==5.2.0
|
django-redis==5.2.0
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ djangorestframework # https://github.com/encode/django-rest-framework
|
||||||
django-cors-headers # https://github.com/adamchainz/django-cors-headers
|
django-cors-headers # https://github.com/adamchainz/django-cors-headers
|
||||||
# DRF-spectacular for api documentation
|
# DRF-spectacular for api documentation
|
||||||
drf-spectacular
|
drf-spectacular
|
||||||
|
django-htmx
|
||||||
|
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
gunicorn
|
gunicorn
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,15 @@ django==3.2.12
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# django-cors-headers
|
# django-cors-headers
|
||||||
|
# django-htmx
|
||||||
# django-model-utils
|
# django-model-utils
|
||||||
# django-redis
|
# django-redis
|
||||||
# djangorestframework
|
# djangorestframework
|
||||||
# drf-spectacular
|
# drf-spectacular
|
||||||
django-cors-headers==3.11.0
|
django-cors-headers==3.11.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
django-htmx==1.8.0
|
||||||
|
# via -r requirements.in
|
||||||
django-model-utils==4.2.0
|
django-model-utils==4.2.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-redis==5.2.0
|
django-redis==5.2.0
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
|
|
||||||
|
|
||||||
class SimpleUserFactory(factory.django.DjangoModelFactory):
|
class UserFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = get_user_model()
|
model = get_user_model()
|
||||||
django_get_or_create = ("username",)
|
django_get_or_create = ("username",)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,39 @@
|
||||||
|
# Register your models here.
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
# Register your models here.
|
from .models import SimpleTask, SimpleList
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SimpleList)
|
||||||
|
class SimpleListAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"title",
|
||||||
|
"user",
|
||||||
|
"created",
|
||||||
|
]
|
||||||
|
list_filter = [
|
||||||
|
"user",
|
||||||
|
]
|
||||||
|
search_fields = [
|
||||||
|
"title",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SimpleTask)
|
||||||
|
class SimpleTaskAdmin(admin.ModelAdmin):
|
||||||
|
date_hierarchy = "deadline"
|
||||||
|
list_display = [
|
||||||
|
"title",
|
||||||
|
"deadline",
|
||||||
|
"created",
|
||||||
|
"list",
|
||||||
|
"done",
|
||||||
|
]
|
||||||
|
list_filter = [
|
||||||
|
"list",
|
||||||
|
"done",
|
||||||
|
]
|
||||||
|
search_fields = [
|
||||||
|
"title",
|
||||||
|
"text",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class SimpletodoConfig(AppConfig):
|
class SimpletodoConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = 'vbv_lernwelt.simpletodo'
|
name = "vbv_lernwelt.simpletodo"
|
||||||
|
|
|
||||||
|
|
@ -18,32 +18,88 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='SimpleList',
|
name="SimpleList",
|
||||||
fields=[
|
fields=[
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
(
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
"created",
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
model_utils.fields.AutoCreatedField(
|
||||||
('title', models.CharField(max_length=255)),
|
default=django.utils.timezone.now,
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
editable=False,
|
||||||
|
verbose_name="created",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
editable=False,
|
||||||
|
verbose_name="modified",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("title", models.CharField(max_length=255)),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='SimpleTask',
|
name="SimpleTask",
|
||||||
fields=[
|
fields=[
|
||||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
(
|
||||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
"created",
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
model_utils.fields.AutoCreatedField(
|
||||||
('title', models.CharField(max_length=255)),
|
default=django.utils.timezone.now,
|
||||||
('text', models.TextField(blank=True, default='')),
|
editable=False,
|
||||||
('done', models.BooleanField(default=False)),
|
verbose_name="created",
|
||||||
('deadline', models.DateTimeField(blank=True, null=True)),
|
),
|
||||||
('list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='simpletodo.simplelist')),
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
editable=False,
|
||||||
|
verbose_name="modified",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("title", models.CharField(max_length=255)),
|
||||||
|
("text", models.TextField(blank=True, default="")),
|
||||||
|
("done", models.BooleanField(default=False)),
|
||||||
|
("deadline", models.DateTimeField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"list",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="simpletodo.simplelist",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ class SimpleList(TimeStampedModel):
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.title} ({self.user})"
|
||||||
|
|
||||||
|
|
||||||
class SimpleTask(TimeStampedModel):
|
class SimpleTask(TimeStampedModel):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
|
||||||
|
from vbv_lernwelt.simpletodo.models import SimpleTask, SimpleList
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleTaskSerializer(ModelSerializer):
|
||||||
|
list_title = serializers.CharField(max_length=100)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SimpleTask
|
||||||
|
fields = ['id', 'title', 'text', 'done', 'deadline', 'list_title', ]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
user = validated_data.pop('user', None)
|
||||||
|
if user is None:
|
||||||
|
raise serializers.ValidationError('User is required')
|
||||||
|
|
||||||
|
list_title = validated_data.pop('list_title')
|
||||||
|
simple_list, _ = SimpleList.objects.get_or_create(title=list_title, user=user)
|
||||||
|
|
||||||
|
validated_data['list'] = simple_list
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<h1>Hello Todos</h1>
|
||||||
|
|
||||||
|
{% for list in simple_lists %}
|
||||||
|
{% include "simpletodo/partials/simple_list.html" with list=list%}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<form class="flex mt-4" action="/todo/api/tasks/" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input class="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-gray-darker"
|
||||||
|
type="text"
|
||||||
|
name="title"
|
||||||
|
maxlength="100"
|
||||||
|
required
|
||||||
|
placeholder="Add Todo"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="list_title" value="{{ list.title }}">
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Add"
|
||||||
|
hx-post="/todo/api/tasks/"
|
||||||
|
hx-trigger="submit"
|
||||||
|
hx-target="#parent-div"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
class="flex-no-shrink p-2 border-2 rounded text-blue-500 border-blue-500 hover:text-white hover:bg-blue-500"
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div class="h-100 w-full flex items-center justify-center bg-blue-100 font-sans">
|
||||||
|
|
||||||
|
<div class="bg-white rounded shadow p-6 m-4 w-full lg:w-3/5 md:w-3/4">
|
||||||
|
<div class="mb-4">
|
||||||
|
<h2 class="text-gray-darkest">{{ list.title }}</h2>
|
||||||
|
{% include "simpletodo/partials/add_task_form.html" with task=task %}
|
||||||
|
</div>
|
||||||
|
{% for task in list.simpletask_set.all %}
|
||||||
|
{% include "simpletodo/partials/task.html" with task=task %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<div class="task">
|
||||||
|
<div class="flex mb-4 items-center">
|
||||||
|
{% if task.done %}
|
||||||
|
<p class="flex-auto text-blue-500 line-through">
|
||||||
|
{% else %}
|
||||||
|
<p class="flex-auto text-blue-900">
|
||||||
|
{% endif %}
|
||||||
|
{{ task.title }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
hx-post="/todo/api/tasks/{{ task.id }}/toggle_done/"
|
||||||
|
hx-swap="outerHTML swap:0.5s"
|
||||||
|
hx-target="closest .task"
|
||||||
|
{% if task.done %}
|
||||||
|
class="flex-no-shrink p-2 ml-4 mr-2 border-2 rounded hover:text-white text-gray-500 border-gray-500 hover:bg-gray-500"
|
||||||
|
{% else %}
|
||||||
|
class="flex-no-shrink p-2 ml-4 mr-2 border-2 rounded hover:text-white text-green-500 border-green-500 hover:bg-green-500"
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
|
{% if task.done %}
|
||||||
|
Not Done
|
||||||
|
{% else %}
|
||||||
|
Done
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
hx-delete="/todo/api/tasks/{{ task.id }}/"
|
||||||
|
hx-swap="outerHTML swap:0.5s"
|
||||||
|
hx-target="closest .task"
|
||||||
|
class="flex-no-shrink p-2 ml-2 border-2 rounded text-red-500 border-red-500 hover:text-white hover:bg-red-500">
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.tests.factories import UserFactory
|
||||||
|
from vbv_lernwelt.simpletodo.models import SimpleTask
|
||||||
|
from vbv_lernwelt.simpletodo.serializers import SimpleTaskSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleTaskSerializerTestCase(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.user = UserFactory()
|
||||||
|
|
||||||
|
def test_serializer(self):
|
||||||
|
serializer = SimpleTaskSerializer(data={
|
||||||
|
'title': 'Test',
|
||||||
|
'list_title': 'Todos',
|
||||||
|
})
|
||||||
|
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save(user=self.user)
|
||||||
|
|
||||||
|
task = SimpleTask.objects.first()
|
||||||
|
|
||||||
|
self.assertEqual(task.title, 'Test')
|
||||||
|
self.assertEqual(task.list.title, 'Todos')
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.conf.urls import url, include
|
||||||
|
from django.urls import path
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'tasks', views.SimpleTaskViewSet, basename='tasks')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", views.index, name="index"),
|
||||||
|
url(r'^api/', include(router.urls)),
|
||||||
|
]
|
||||||
|
|
@ -1,3 +1,85 @@
|
||||||
from django.shortcuts import render
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from rest_framework import viewsets, status
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.renderers import TemplateHTMLRenderer, JSONRenderer
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
# Create your views here.
|
from vbv_lernwelt.simpletodo.models import SimpleList, SimpleTask
|
||||||
|
from vbv_lernwelt.simpletodo.serializers import SimpleTaskSerializer
|
||||||
|
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
simple_lists = SimpleList.objects.filter(user=request.user)
|
||||||
|
return render(request, 'simpletodo/index.html', {
|
||||||
|
'simple_lists': simple_lists
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleTaskViewSet(viewsets.ModelViewSet):
|
||||||
|
serializer_class = SimpleTaskSerializer
|
||||||
|
renderer_classes = [TemplateHTMLRenderer, JSONRenderer]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
return SimpleTask.objects.filter(list__user=user)
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
|
||||||
|
if request.accepted_renderer.format == 'html':
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
else:
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
serializer.save(user=request.user)
|
||||||
|
|
||||||
|
if request.accepted_renderer.format == 'html':
|
||||||
|
return redirect("/todo/")
|
||||||
|
|
||||||
|
headers = self.get_success_headers(serializer.data)
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
instance.delete()
|
||||||
|
|
||||||
|
if request.htmx:
|
||||||
|
return HttpResponse(status=200, content='')
|
||||||
|
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post',])
|
||||||
|
def toggle_done(self, request, pk=None):
|
||||||
|
task = self.get_object()
|
||||||
|
task.done = not task.done
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
if request.htmx:
|
||||||
|
return render(request, 'simpletodo/partials/task.html', {
|
||||||
|
'task': task
|
||||||
|
})
|
||||||
|
|
||||||
|
return Response(self.get_serializer(task), status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
#
|
||||||
|
# def get_category_from_request(self, request):
|
||||||
|
# cat_name = request.query_params.get('cat_name')
|
||||||
|
# category_obj = None
|
||||||
|
#
|
||||||
|
# if cat_name:
|
||||||
|
# category_obj = VideoCategory.objects.filter(category__iexact=cat_name).first()
|
||||||
|
# if not category_obj:
|
||||||
|
# category_obj = VideoCategory.objects.first()
|
||||||
|
#
|
||||||
|
# return category_obj
|
||||||
|
#
|
||||||
|
# @action(detail=False, methods=['get'])
|
||||||
|
# def form(self, request):
|
||||||
|
# category_obj = self.get_category_from_request(request)
|
||||||
|
# return Response(template_name='videos/partials/video_form.html', data={'category': category_obj})
|
||||||
|
#
|
||||||
|
# @action(detail=False, methods=['get'])
|
||||||
|
# def cancel(self, request):
|
||||||
|
# category_obj = self.get_category_from_request(request)
|
||||||
|
# return Response(template_name='videos/partials/show_add_form.html', data={'category': category_obj})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
.task.htmx-swapping {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 1s ease-out;
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,9 @@
|
||||||
<!-- Le javascript
|
<!-- Le javascript
|
||||||
================================================== -->
|
================================================== -->
|
||||||
{# Placed at the top of the document so pages load faster with defer #}
|
{# Placed at the top of the document so pages load faster with defer #}
|
||||||
|
<script defer src="https://unpkg.com/htmx.org@1.6.1"></script>
|
||||||
|
|
||||||
|
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<!-- Your stuff: Third-party javascript libraries go here -->
|
<!-- Your stuff: Third-party javascript libraries go here -->
|
||||||
<!-- place project specific Javascript in this file -->
|
<!-- place project specific Javascript in this file -->
|
||||||
|
|
@ -57,115 +60,19 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto">
|
|
||||||
<div class="flex justify-between flex-col md:flex-row">
|
|
||||||
<img class="w-full md:w-2/4" src="https://www.thezebra.com/insurance-news/wp-content/uploads/2016/01/Tree-fallen-on-car-1024x682.jpeg"/>
|
|
||||||
<div class="w-full md:w-2/4 flex flex-col justify-center p-4 md:p-16">
|
|
||||||
<h2 class="text-xl md:text-3xl font-bold">Machen Sie hier eine Selbstevaluation</h2>
|
|
||||||
<p class="my-4 text-xl">Hier steht noch etwas mehr Text</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container mx-auto bg-blue-100 border-t-2 border-gray-900">
|
|
||||||
<div class="p-8 flex flex-col md:flex-row">
|
|
||||||
|
|
||||||
<div class="w-full md:w-1/2 xl:w-1/3 px-4">
|
|
||||||
<div class="bg-white rounded-lg overflow-hidden mb-10 shadow-md">
|
|
||||||
<img
|
|
||||||
src="https://cdn.tailgrids.com/1.0/assets/images/cards/card-01/image-01.jpg"
|
|
||||||
alt="image"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
<div class="p-8 sm:p-9 md:p-7 xl:p-9 text-center">
|
|
||||||
<a
|
|
||||||
href="javascript:void(0)"
|
|
||||||
class="
|
|
||||||
inline-block
|
|
||||||
py-2
|
|
||||||
px-7
|
|
||||||
border border-[#E5E7EB]
|
|
||||||
rounded-full
|
|
||||||
text-base text-body-color
|
|
||||||
font-medium
|
|
||||||
hover:border-primary hover:bg-primary hover:text-white
|
|
||||||
transition
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Kurs X
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-full md:w-1/2 xl:w-1/3 px-4">
|
|
||||||
<div class="bg-white rounded-lg overflow-hidden mb-10 shadow-md">
|
|
||||||
<img
|
|
||||||
src="https://cdn.tailgrids.com/1.0/assets/images/cards/card-01/image-02.jpg"
|
|
||||||
alt="image"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
<div class="p-8 sm:p-9 md:p-7 xl:p-9 text-center">
|
|
||||||
<a
|
|
||||||
href="javascript:void(0)"
|
|
||||||
class="
|
|
||||||
inline-block
|
|
||||||
py-2
|
|
||||||
px-7
|
|
||||||
border border-[#E5E7EB]
|
|
||||||
rounded-full
|
|
||||||
text-base text-body-color
|
|
||||||
font-medium
|
|
||||||
hover:border-primary hover:bg-primary hover:text-white
|
|
||||||
transition
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Kurs Y
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-full md:w-1/2 xl:w-1/3 px-4">
|
|
||||||
<div class="bg-white rounded-lg overflow-hidden mb-10 shadow-md">
|
|
||||||
<img
|
|
||||||
src="https://cdn.tailgrids.com/1.0/assets/images/cards/card-01/image-03.jpg"
|
|
||||||
alt="image"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
<div class="p-8 sm:p-9 md:p-7 xl:p-9 text-center">
|
|
||||||
<a
|
|
||||||
href="javascript:void(0)"
|
|
||||||
class="
|
|
||||||
inline-block
|
|
||||||
py-2
|
|
||||||
px-7
|
|
||||||
border border-[#E5E7EB]
|
|
||||||
rounded-full
|
|
||||||
text-base text-body-color
|
|
||||||
font-medium
|
|
||||||
hover:border-primary hover:bg-primary hover:text-white
|
|
||||||
transition
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Kurs Z
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="p-8 text-right">weitere Kurse entdecken und buchen</p>
|
|
||||||
|
|
||||||
<div class="border-t-2 border-blue-400 m-8 p-8"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
{% block modal %}{% endblock modal %}
|
{% block modal %}{% endblock modal %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.body.addEventListener('htmx:configRequest', (event) => {
|
||||||
|
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
{% block inline_javascript %}
|
{% block inline_javascript %}
|
||||||
|
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Script tags with only code, no src (defer by default). To run
|
Script tags with only code, no src (defer by default). To run
|
||||||
with a "defer" so that you run inline code:
|
with a "defer" so that you run inline code:
|
||||||
|
|
|
||||||
|
|
@ -1 +1,108 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<div class="flex justify-between flex-col md:flex-row">
|
||||||
|
<img class="w-full md:w-2/4"
|
||||||
|
src="https://www.thezebra.com/insurance-news/wp-content/uploads/2016/01/Tree-fallen-on-car-1024x682.jpeg"/>
|
||||||
|
<div class="w-full md:w-2/4 flex flex-col justify-center p-4 md:p-16">
|
||||||
|
<h2 class="text-xl md:text-3xl font-bold">Machen Sie hier eine Selbstevaluation</h2>
|
||||||
|
<p class="my-4 text-xl">Hier steht noch etwas mehr Text</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container mx-auto bg-blue-100 border-t-2 border-gray-900">
|
||||||
|
<div class="p-8 flex flex-col md:flex-row">
|
||||||
|
|
||||||
|
<div class="w-full md:w-1/2 xl:w-1/3 px-4">
|
||||||
|
<div class="bg-white rounded-lg overflow-hidden mb-10 shadow-md">
|
||||||
|
<img
|
||||||
|
src="https://cdn.tailgrids.com/1.0/assets/images/cards/card-01/image-01.jpg"
|
||||||
|
alt="image"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
<div class="p-8 sm:p-9 md:p-7 xl:p-9 text-center">
|
||||||
|
<a
|
||||||
|
href="javascript:void(0)"
|
||||||
|
class="
|
||||||
|
inline-block
|
||||||
|
py-2
|
||||||
|
px-7
|
||||||
|
border border-[#E5E7EB]
|
||||||
|
rounded-full
|
||||||
|
text-base text-body-color
|
||||||
|
font-medium
|
||||||
|
hover:border-primary hover:bg-primary hover:text-white
|
||||||
|
transition
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Kurs X
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full md:w-1/2 xl:w-1/3 px-4">
|
||||||
|
<div class="bg-white rounded-lg overflow-hidden mb-10 shadow-md">
|
||||||
|
<img
|
||||||
|
src="https://cdn.tailgrids.com/1.0/assets/images/cards/card-01/image-02.jpg"
|
||||||
|
alt="image"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
<div class="p-8 sm:p-9 md:p-7 xl:p-9 text-center">
|
||||||
|
<a
|
||||||
|
href="javascript:void(0)"
|
||||||
|
class="
|
||||||
|
inline-block
|
||||||
|
py-2
|
||||||
|
px-7
|
||||||
|
border border-[#E5E7EB]
|
||||||
|
rounded-full
|
||||||
|
text-base text-body-color
|
||||||
|
font-medium
|
||||||
|
hover:border-primary hover:bg-primary hover:text-white
|
||||||
|
transition
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Kurs Y
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full md:w-1/2 xl:w-1/3 px-4">
|
||||||
|
<div class="bg-white rounded-lg overflow-hidden mb-10 shadow-md">
|
||||||
|
<img
|
||||||
|
src="https://cdn.tailgrids.com/1.0/assets/images/cards/card-01/image-03.jpg"
|
||||||
|
alt="image"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
<div class="p-8 sm:p-9 md:p-7 xl:p-9 text-center">
|
||||||
|
<a
|
||||||
|
href="javascript:void(0)"
|
||||||
|
class="
|
||||||
|
inline-block
|
||||||
|
py-2
|
||||||
|
px-7
|
||||||
|
border border-[#E5E7EB]
|
||||||
|
rounded-full
|
||||||
|
text-base text-body-color
|
||||||
|
font-medium
|
||||||
|
hover:border-primary hover:bg-primary hover:text-white
|
||||||
|
transition
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Kurs Z
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="p-8 text-right">weitere Kurse entdecken und buchen</p>
|
||||||
|
|
||||||
|
<div class="border-t-2 border-blue-400 m-8 p-8"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue