work + report draft
This commit is contained in:
parent
43fe9aea31
commit
7a70163e76
42 changed files with 228 additions and 19 deletions
0
.gitignore
vendored
Executable file → Normal file
0
.gitignore
vendored
Executable file → Normal file
0
azube/__init__.py
Executable file → Normal file
0
azube/__init__.py
Executable file → Normal file
0
azube/asgi.py
Executable file → Normal file
0
azube/asgi.py
Executable file → Normal file
0
azube/settings.py
Executable file → Normal file
0
azube/settings.py
Executable file → Normal file
0
azube/urls.py
Executable file → Normal file
0
azube/urls.py
Executable file → Normal file
0
azube/wsgi.py
Executable file → Normal file
0
azube/wsgi.py
Executable file → Normal file
0
core/__init__.py
Executable file → Normal file
0
core/__init__.py
Executable file → Normal file
0
core/admin.py
Executable file → Normal file
0
core/admin.py
Executable file → Normal file
0
core/apps.py
Executable file → Normal file
0
core/apps.py
Executable file → Normal file
0
core/azure_auth.py
Executable file → Normal file
0
core/azure_auth.py
Executable file → Normal file
0
core/doc_gen.py
Executable file → Normal file
0
core/doc_gen.py
Executable file → Normal file
1
core/migrations/0001_initial.py
Executable file → Normal file
1
core/migrations/0001_initial.py
Executable file → Normal file
|
@ -6,7 +6,6 @@ import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
1
core/migrations/0002_alter_berichtsheft_id.py
Executable file → Normal file
1
core/migrations/0002_alter_berichtsheft_id.py
Executable file → Normal file
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0001_initial"),
|
("core", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
1
core/migrations/0003_group_azureuser_alter_berichtsheft_user.py
Executable file → Normal file
1
core/migrations/0003_group_azureuser_alter_berichtsheft_user.py
Executable file → Normal file
|
@ -8,7 +8,6 @@ import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
("auth", "0012_alter_user_first_name_max_length"),
|
||||||
("core", "0002_alter_berichtsheft_id"),
|
("core", "0002_alter_berichtsheft_id"),
|
||||||
|
|
1
core/migrations/0004_remove_berichtsheft_approved_by.py
Executable file → Normal file
1
core/migrations/0004_remove_berichtsheft_approved_by.py
Executable file → Normal file
|
@ -4,7 +4,6 @@ from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0003_group_azureuser_alter_berichtsheft_user"),
|
("core", "0003_group_azureuser_alter_berichtsheft_user"),
|
||||||
]
|
]
|
||||||
|
|
1
core/migrations/0005_user_alter_berichtsheft_user_delete_azureuser.py
Executable file → Normal file
1
core/migrations/0005_user_alter_berichtsheft_user_delete_azureuser.py
Executable file → Normal file
|
@ -7,7 +7,6 @@ import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
("auth", "0012_alter_user_first_name_max_length"),
|
||||||
("core", "0004_remove_berichtsheft_approved_by"),
|
("core", "0004_remove_berichtsheft_approved_by"),
|
||||||
|
|
1
core/migrations/0006_berichtsheft_num.py
Executable file → Normal file
1
core/migrations/0006_berichtsheft_num.py
Executable file → Normal file
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0005_user_alter_berichtsheft_user_delete_azureuser"),
|
("core", "0005_user_alter_berichtsheft_user_delete_azureuser"),
|
||||||
]
|
]
|
||||||
|
|
1
core/migrations/0007_approval.py
Executable file → Normal file
1
core/migrations/0007_approval.py
Executable file → Normal file
|
@ -5,7 +5,6 @@ import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0006_berichtsheft_num"),
|
("core", "0006_berichtsheft_num"),
|
||||||
]
|
]
|
||||||
|
|
1
core/migrations/0008_berichtsheft_needs_rewrite.py
Executable file → Normal file
1
core/migrations/0008_berichtsheft_needs_rewrite.py
Executable file → Normal file
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0007_approval"),
|
("core", "0007_approval"),
|
||||||
]
|
]
|
||||||
|
|
1
core/migrations/0009_berichtsheft_kind.py
Executable file → Normal file
1
core/migrations/0009_berichtsheft_kind.py
Executable file → Normal file
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0008_berichtsheft_needs_rewrite"),
|
("core", "0008_berichtsheft_needs_rewrite"),
|
||||||
]
|
]
|
||||||
|
|
1
core/migrations/0010_berichtsheft_department.py
Executable file → Normal file
1
core/migrations/0010_berichtsheft_department.py
Executable file → Normal file
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0009_berichtsheft_kind"),
|
("core", "0009_berichtsheft_kind"),
|
||||||
]
|
]
|
||||||
|
|
20
core/migrations/0011_berichtsheftdraft.py
Normal file
20
core/migrations/0011_berichtsheftdraft.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.2.17 on 2024-12-05 10:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("core", "0010_berichtsheft_department"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="BerichtsheftDraft",
|
||||||
|
fields=[
|
||||||
|
("user", models.TextField(primary_key=True, serialize=False)),
|
||||||
|
("department", models.CharField(default="", max_length=160)),
|
||||||
|
("content", models.JSONField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
core/migrations/__init__.py
Executable file → Normal file
0
core/migrations/__init__.py
Executable file → Normal file
6
core/models.py
Executable file → Normal file
6
core/models.py
Executable file → Normal file
|
@ -55,3 +55,9 @@ class Approval(models.Model):
|
||||||
report = models.ForeignKey(
|
report = models.ForeignKey(
|
||||||
Berichtsheft, on_delete=models.CASCADE, related_name="report"
|
Berichtsheft, on_delete=models.CASCADE, related_name="report"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BerichtsheftDraft(models.Model):
|
||||||
|
user = models.TextField(primary_key=True)
|
||||||
|
department = models.CharField(max_length=160, default="")
|
||||||
|
content = models.JSONField()
|
||||||
|
|
4
core/reports.py
Executable file → Normal file
4
core/reports.py
Executable file → Normal file
|
@ -20,7 +20,7 @@ class WeeklyReport(forms.Form):
|
||||||
widget=forms.Textarea(
|
widget=forms.Textarea(
|
||||||
attrs={
|
attrs={
|
||||||
"class": "w-full p-2 border border-gray-300 rounded-lg focus:ring focus:ring-blue-400 mb-5",
|
"class": "w-full p-2 border border-gray-300 rounded-lg focus:ring focus:ring-blue-400 mb-5",
|
||||||
"rows": 10,
|
"rows": 5,
|
||||||
"placeholder": "Betriebliche Tätigkeiten",
|
"placeholder": "Betriebliche Tätigkeiten",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -31,7 +31,7 @@ class WeeklyReport(forms.Form):
|
||||||
widget=forms.Textarea(
|
widget=forms.Textarea(
|
||||||
attrs={
|
attrs={
|
||||||
"class": "w-full p-2 border border-gray-300 rounded-lg focus:ring focus:ring-blue-400 mb-5",
|
"class": "w-full p-2 border border-gray-300 rounded-lg focus:ring focus:ring-blue-400 mb-5",
|
||||||
"rows": 8,
|
"rows": 10,
|
||||||
"placeholder": "Thema der Woche",
|
"placeholder": "Thema der Woche",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
0
core/styles.py
Executable file → Normal file
0
core/styles.py
Executable file → Normal file
0
core/templates/component/report.html
Executable file → Normal file
0
core/templates/component/report.html
Executable file → Normal file
0
core/templates/head.html
Executable file → Normal file
0
core/templates/head.html
Executable file → Normal file
2
core/templates/header.html
Executable file → Normal file
2
core/templates/header.html
Executable file → Normal file
|
@ -1,4 +1,4 @@
|
||||||
<header class="w-full bg-red-600 text-white py-4 shadow-md">
|
<header class="w-full bg-red-600 text-white py-4 shadow-md max-h-20 h-full max-h-20">
|
||||||
<div class="max-w-4xl {% if center is None or center %}mx-auto{% endif %} px-4">
|
<div class="max-w-4xl {% if center is None or center %}mx-auto{% endif %} px-4">
|
||||||
<h1 class="text-2xl font-bold {% if center is None or center %}{% else %}ml-5{% endif %}">{{ title }}</h1>
|
<h1 class="text-2xl font-bold {% if center is None or center %}{% else %}ml-5{% endif %}">{{ title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
0
core/templates/htmx/reports.html
Executable file → Normal file
0
core/templates/htmx/reports.html
Executable file → Normal file
0
core/templates/index.html
Executable file → Normal file
0
core/templates/index.html
Executable file → Normal file
0
core/templates/report.html
Executable file → Normal file
0
core/templates/report.html
Executable file → Normal file
91
core/templates/write.html
Executable file → Normal file
91
core/templates/write.html
Executable file → Normal file
|
@ -19,13 +19,25 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" class="space-y-4">
|
<form method="post" class="space-y-4">
|
||||||
|
|
||||||
|
{% load set_content %}
|
||||||
|
{% load access %}
|
||||||
|
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="{{ field.id_for_label }}">
|
<label for="{{ field.id_for_label }}">
|
||||||
{{ field.label|safe }}
|
{{ field.label|safe }}
|
||||||
</label>
|
</label>
|
||||||
{{ field }}
|
|
||||||
|
{% if field.id_for_label == "id_department" %}
|
||||||
|
|
||||||
|
{{ field|set_content:draft.department}}
|
||||||
|
|
||||||
|
{%else%}
|
||||||
|
{% with content=draft.content|access:field.id_for_label %}
|
||||||
|
{{ field|set_content:content }}
|
||||||
|
{% endwith %}
|
||||||
|
{%endif%}
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<p class="text-red-500 text-sm">{{ field.errors|join:", " }}</p>
|
<p class="text-red-500 text-sm">{{ field.errors|join:", " }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -39,6 +51,81 @@
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function updateDraft() {
|
||||||
|
const formData = {};
|
||||||
|
|
||||||
|
{% for field in form %}
|
||||||
|
|
||||||
|
var field = document.getElementById("{{ field.id_for_label }}");
|
||||||
|
formData["{{ field.id_for_label }}"] = field.value;
|
||||||
|
|
||||||
|
{% endfor%}
|
||||||
|
|
||||||
|
fetch("/draft", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': getCSRFToken(),
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData),
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log('Updated Draft:', data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function debounce(func, delay) {
|
||||||
|
let timeout;
|
||||||
|
return function (...args) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func.apply(this, args), delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCSRFToken() {
|
||||||
|
const name = 'csrftoken';
|
||||||
|
const cookies = document.cookie.split(';');
|
||||||
|
for (let cookie of cookies) {
|
||||||
|
cookie = cookie.trim();
|
||||||
|
if (cookie.startsWith(name + '=')) {
|
||||||
|
return cookie.substring(name.length + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupListeners() {
|
||||||
|
const textareas = document.querySelectorAll('textarea');
|
||||||
|
const inputs = document.querySelectorAll('input');
|
||||||
|
|
||||||
|
const debouncedSend = debounce(() => updateDraft(), 500);
|
||||||
|
|
||||||
|
textareas.forEach(textarea => {
|
||||||
|
textarea.addEventListener('input', () => debouncedSend());
|
||||||
|
});
|
||||||
|
|
||||||
|
inputs.forEach(input => {
|
||||||
|
if (input.type === 'text' || input.type === 'email' || input.type === 'password' || input.type === 'hidden') {
|
||||||
|
input.addEventListener('input', () => debouncedSend());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setupListeners();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
13
core/templatetags/access.py
Normal file
13
core/templatetags/access.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="access")
|
||||||
|
def access(value, arg):
|
||||||
|
if value == None:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
return value[arg]
|
||||||
|
except:
|
||||||
|
return ""
|
25
core/templatetags/add_attr.py
Normal file
25
core/templatetags/add_attr.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="add_attr")
|
||||||
|
def add_attr(field, css):
|
||||||
|
"""Adds custom attributes (like class) to form fields without overwriting existing attributes."""
|
||||||
|
# Retrieve existing attributes (if any)
|
||||||
|
attrs = field.field.widget.attrs.copy()
|
||||||
|
|
||||||
|
# Split the CSS string into key-value pairs
|
||||||
|
definition = css.split(",")
|
||||||
|
|
||||||
|
for d in definition:
|
||||||
|
if ":" not in d:
|
||||||
|
# If no key-value pair is provided, default to 'class'
|
||||||
|
attrs["class"] = f"{attrs.get('class', '')} {d}".strip()
|
||||||
|
else:
|
||||||
|
# If a key-value pair is provided (e.g., 'data-test:123'), add it
|
||||||
|
key, val = d.split(":")
|
||||||
|
attrs[key.strip()] = val.strip()
|
||||||
|
|
||||||
|
# Return the widget with the updated attributes
|
||||||
|
return field.as_widget(attrs=attrs)
|
27
core/templatetags/set_content.py
Normal file
27
core/templatetags/set_content.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from django import template
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="set_content")
|
||||||
|
def set_content(field, content):
|
||||||
|
"""Sets the content of input and textarea fields, and custom attributes like class, without overwriting existing attributes."""
|
||||||
|
|
||||||
|
# Retrieve existing attributes (if any)
|
||||||
|
attrs = field.field.widget.attrs.copy()
|
||||||
|
|
||||||
|
# Check if the field is a text input or textarea
|
||||||
|
is_input_or_textarea = isinstance(
|
||||||
|
field.field.widget, (forms.widgets.TextInput, forms.widgets.Textarea)
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the field is an input or textarea, set the content as value or text
|
||||||
|
if is_input_or_textarea:
|
||||||
|
attrs["value"] = content.strip() # Set the value to the provided content
|
||||||
|
|
||||||
|
if isinstance(field.field.widget, forms.widgets.Textarea):
|
||||||
|
field.initial = content.strip()
|
||||||
|
|
||||||
|
# Return the widget with the updated attributes
|
||||||
|
return field.as_widget(attrs=attrs)
|
0
core/tests.py
Executable file → Normal file
0
core/tests.py
Executable file → Normal file
1
core/urls.py
Executable file → Normal file
1
core/urls.py
Executable file → Normal file
|
@ -6,4 +6,5 @@ urlpatterns = [
|
||||||
path("write", views.write_new_report, name="write"),
|
path("write", views.write_new_report, name="write"),
|
||||||
path("report/<int:report_id>", views.report_detail_page, name="report_detail"),
|
path("report/<int:report_id>", views.report_detail_page, name="report_detail"),
|
||||||
path("reports", views.reports_list, name="reports_list"),
|
path("reports", views.reports_list, name="reports_list"),
|
||||||
|
path("draft", views.report_draft_update, name="report_draft_update"),
|
||||||
]
|
]
|
||||||
|
|
0
core/util.py
Executable file → Normal file
0
core/util.py
Executable file → Normal file
48
core/views.py
Executable file → Normal file
48
core/views.py
Executable file → Normal file
|
@ -7,6 +7,7 @@ from .models import Berichtsheft
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from core.styles import STYLE
|
from core.styles import STYLE
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
|
@ -78,11 +79,15 @@ def write_new_report_get(request):
|
||||||
if latest.year == year_now and latest.week == week_now:
|
if latest.year == year_now and latest.week == week_now:
|
||||||
return redirect(f"/report/{latest.id}")
|
return redirect(f"/report/{latest.id}")
|
||||||
|
|
||||||
current_year, current_week, start_date, end_date, current_num = (
|
(
|
||||||
get_current_report_values(latest)
|
current_year,
|
||||||
)
|
current_week,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
current_num,
|
||||||
|
) = get_current_report_values(latest)
|
||||||
|
|
||||||
# TODO : Cookies for persistent saves
|
draft = BerichtsheftDraft.objects.filter(user=user.id).first()
|
||||||
|
|
||||||
form = user.get_report_kind_form()
|
form = user.get_report_kind_form()
|
||||||
|
|
||||||
|
@ -98,6 +103,7 @@ def write_new_report_get(request):
|
||||||
"current_num": current_num,
|
"current_num": current_num,
|
||||||
"report_kind": report_kind,
|
"report_kind": report_kind,
|
||||||
"form": form,
|
"form": form,
|
||||||
|
"draft": draft,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -154,3 +160,37 @@ def report_detail_page(request, report_id):
|
||||||
return HttpResponse("Nah", status=401)
|
return HttpResponse("Nah", status=401)
|
||||||
|
|
||||||
return render(request, "report.html", {"report": report})
|
return render(request, "report.html", {"report": report})
|
||||||
|
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from .models import BerichtsheftDraft
|
||||||
|
|
||||||
|
|
||||||
|
def report_draft_update(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
user = AzureUser(request)
|
||||||
|
|
||||||
|
data = json.loads(request.body)
|
||||||
|
|
||||||
|
department = ""
|
||||||
|
content = {}
|
||||||
|
|
||||||
|
for key, val in data.items():
|
||||||
|
if key == "id_department":
|
||||||
|
department = val
|
||||||
|
continue
|
||||||
|
content[key] = val
|
||||||
|
|
||||||
|
draft, created = BerichtsheftDraft.objects.update_or_create(
|
||||||
|
user=user.id, defaults={"department": department, "content": content}
|
||||||
|
)
|
||||||
|
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"message": "Draft updated" if not created else "Draft created",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return JsonResponse({"error": "Invalid request method"}, status=400)
|
||||||
|
|
0
manage.py
Executable file → Normal file
0
manage.py
Executable file → Normal file
0
requirements.txt
Executable file → Normal file
0
requirements.txt
Executable file → Normal file
Loading…
Add table
Reference in a new issue