Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,10 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MasterdataConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "masterdata"
|
||||
|
||||
def ready(self):
|
||||
from .loader import autodiscover
|
||||
autodiscover()
|
||||
@@ -0,0 +1,26 @@
|
||||
import importlib
|
||||
|
||||
from django.apps import apps
|
||||
|
||||
|
||||
_loaded = False
|
||||
|
||||
|
||||
def autodiscover():
|
||||
|
||||
global _loaded
|
||||
|
||||
if _loaded:
|
||||
return
|
||||
|
||||
_loaded = True
|
||||
|
||||
for app in apps.get_app_configs():
|
||||
|
||||
try:
|
||||
importlib.import_module(
|
||||
f"{app.name}.settings_registry"
|
||||
)
|
||||
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -0,0 +1,14 @@
|
||||
MASTERDATA = []
|
||||
|
||||
|
||||
def register_setting(group,name,model,setting_type="table"):
|
||||
|
||||
MASTERDATA.append({
|
||||
"group": group,
|
||||
"name": name,
|
||||
"model": model,
|
||||
"model_name": model._meta.model_name,
|
||||
"verbose_name": model._meta.verbose_name,
|
||||
"verbose_name_plural": model._meta.verbose_name_plural,
|
||||
"type": setting_type,
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Einstellungen
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block header_title %}
|
||||
{{ title }} hinzufügen
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
Einstellungen der App und Module bearbeiten
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
|
||||
<form method="post" class="space-y-4">
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
|
||||
{{ form.as_p }}
|
||||
|
||||
|
||||
<button
|
||||
class="btn btn-save">
|
||||
|
||||
Speichern
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,54 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Einstellungen
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block header_title %}
|
||||
{{ title }} bearbeiten
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
Einstellungen der App und Module bearbeiten
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="mb-8">
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold">Aktionen</h2>
|
||||
<p class="text-slate-400 text-sm mt-1">Einträge hinzufügen</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<a href="{% url 'masterdata:add' model_name %}" class="btn btn-add">+ Eintrag</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
<h2 class="text-xl font-bold">Einträge</h2>
|
||||
{% for obj in objects %}
|
||||
<div class="flex justify-between border-b py-3">
|
||||
<span>{{ obj }}</span>
|
||||
|
||||
<form method="post" action="{% url 'masterdata:delete' model_name obj.id %}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-delete">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="mt-4 bg-white dark:bg-slate-800 border border-orange-500 rounded-xl p-6 text-orange-500">
|
||||
Keine Einträge in der Datenbank
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Einstellungen
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block header_title %}
|
||||
{{ title }} bearbeiten
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
Einstellungen der App und Module bearbeiten
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors">
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="space-y-4">
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form.as_p }}
|
||||
|
||||
|
||||
<button class="btn btn-save">
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,59 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Einstellungen
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block header_title %}
|
||||
Einstellungen
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
Einstellungen der App und Module bearbeiten
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% for group, entries in groups.items %}
|
||||
|
||||
|
||||
|
||||
<div class="p-6 rounded-xl shadow bg-white dark:bg-slate-800 text-slate-900 dark:text-white transition-colors mb-8">
|
||||
|
||||
<div class="mb-6">
|
||||
<h2 class="text-xl font-bold">
|
||||
{{ group }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
|
||||
|
||||
{% for entry in entries %}
|
||||
|
||||
<a
|
||||
href="{% url 'masterdata:detail' entry.model_name %}"
|
||||
class="bg-slate-100 dark:bg-slate-700 rounded-xl p-6"
|
||||
>
|
||||
<h2 class="text-xl font-bold">
|
||||
{{ entry.name }}
|
||||
</h2>
|
||||
<p class="text-sm mt-2">
|
||||
{% if entry.type == "single" %}
|
||||
Einzelne Einstellung
|
||||
{% else %}
|
||||
Verwaltung
|
||||
{% endif %}
|
||||
</p>
|
||||
</a>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,14 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
|
||||
app_name = "masterdata"
|
||||
|
||||
urlpatterns = [
|
||||
path("",views.overview,name="overview"),
|
||||
path("<str:model_name>/",views.setting_detail,name="detail"),
|
||||
path("<str:model_name>/add/",views.setting_add,name="add"),
|
||||
path("<str:model_name>/<int:pk>/delete/",views.setting_delete,name="delete"),
|
||||
path("update/check/", views.update_check, name="update_check"),
|
||||
path("update/apply/", views.update_apply, name="update_apply"),
|
||||
]
|
||||
@@ -0,0 +1,157 @@
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from accounts.decorators import permission_required
|
||||
from collections import defaultdict
|
||||
from .registry import MASTERDATA
|
||||
from django.forms import modelform_factory
|
||||
from django.apps import apps
|
||||
from core.forms.base import BaseForm
|
||||
import subprocess
|
||||
from django.http import JsonResponse
|
||||
from django.conf import settings
|
||||
|
||||
def get_registry_entry(model_name):
|
||||
entry = next(
|
||||
(
|
||||
x for x in MASTERDATA
|
||||
if x["model_name"] == model_name
|
||||
),
|
||||
None
|
||||
)
|
||||
if not entry:
|
||||
raise Http404
|
||||
return entry
|
||||
|
||||
def create_setting_form(model):
|
||||
Meta = type(
|
||||
"Meta",
|
||||
(),
|
||||
{
|
||||
"model": model,
|
||||
"fields": "__all__",
|
||||
}
|
||||
)
|
||||
FormClass = type(
|
||||
f"{model.__name__}Form",
|
||||
(BaseForm,),
|
||||
{
|
||||
"Meta": Meta,
|
||||
|
||||
"__init__": lambda self, *args, **kwargs: (
|
||||
BaseForm.__init__(self, *args, **kwargs),
|
||||
self.apply_style()
|
||||
)[-1]
|
||||
}
|
||||
)
|
||||
return FormClass
|
||||
|
||||
@login_required
|
||||
@permission_required("settings_view")
|
||||
def overview(request):
|
||||
groups = defaultdict(list)
|
||||
for item in MASTERDATA:
|
||||
groups[item["group"]].append(item)
|
||||
return render(request,"masterdata/overview.html",{"groups": dict(groups)})
|
||||
|
||||
@login_required
|
||||
@permission_required("settings_detail_view")
|
||||
def setting_detail(request, model_name):
|
||||
entry = get_registry_entry(model_name)
|
||||
model = entry["model"]
|
||||
if entry["type"] == "single":
|
||||
obj = model.objects.first()
|
||||
Form = create_setting_form(model)
|
||||
if request.method == "POST":
|
||||
form = Form(request.POST,request.FILES,instance=obj)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect("masterdata:detail",model_name=model_name)
|
||||
else:
|
||||
form = Form(instance=obj)
|
||||
return render(request,"masterdata/detail_single.html",{"title": entry["verbose_name_plural"],"form": form,"object": obj,"model_name": model_name,})
|
||||
objects = model.objects.all()
|
||||
return render(request,"masterdata/detail.html",{"title": entry["verbose_name_plural"],"objects": objects,"model_name": model_name,})
|
||||
|
||||
@login_required
|
||||
@permission_required("settings_add")
|
||||
def setting_add(request, model_name):
|
||||
entry = get_registry_entry(model_name)
|
||||
model = entry["model"]
|
||||
if entry["type"] == "single":
|
||||
if model.objects.exists():
|
||||
return redirect("masterdata:detail",model_name=model_name)
|
||||
Form = create_setting_form(model)
|
||||
if request.method == "POST":
|
||||
form = Form(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect("masterdata:detail",model_name=model_name)
|
||||
else:
|
||||
form = Form()
|
||||
return render(request,"masterdata/add.html",{"form": form,"title": f"{entry['verbose_name']}",})
|
||||
|
||||
@login_required
|
||||
@permission_required("settings_delete")
|
||||
def setting_delete(request, model_name, pk):
|
||||
entry = get_registry_entry(model_name)
|
||||
model = entry["model"]
|
||||
if entry["type"] == "single":
|
||||
return redirect("masterdata:detail",model_name=model_name)
|
||||
obj = get_object_or_404(model,pk=pk)
|
||||
if request.method == "POST":
|
||||
obj.delete()
|
||||
return redirect("masterdata:detail",model_name=model_name)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BASE_DIR = settings.BASE_DIR
|
||||
|
||||
def _git(cmd):
|
||||
r = subprocess.run(cmd, capture_output=True, text=True, cwd=BASE_DIR)
|
||||
return r.stdout.strip(), r.returncode
|
||||
|
||||
@login_required
|
||||
@permission_required("settings_update_check")
|
||||
def update_check(request):
|
||||
_git(["git", "fetch", "origin"])
|
||||
local, _ = _git(["git", "rev-parse", "HEAD"])
|
||||
remote, _ = _git(["git", "rev-parse", "origin/main"])
|
||||
log, _ = _git(["git", "log", f"{local}..{remote}", "--oneline"])
|
||||
return JsonResponse({
|
||||
"up_to_date": local == remote,
|
||||
"local": local[:7],
|
||||
"remote": remote[:7],
|
||||
"changes": log.splitlines() if log else [],
|
||||
})
|
||||
|
||||
@login_required
|
||||
@permission_required("settings_update_apply")
|
||||
def update_apply(request):
|
||||
if request.method != "POST":
|
||||
return JsonResponse({"error": "POST required"}, status=405)
|
||||
_, code = _git(["git", "pull", "origin", "main"])
|
||||
if code != 0:
|
||||
return JsonResponse({"error": "git pull fehlgeschlagen"}, status=500)
|
||||
_git(["pip", "install", "-r", f"{BASE_DIR}/requirements.txt", "-q"])
|
||||
_git(["python", "manage.py", "migrate", "--no-input"])
|
||||
return JsonResponse({"success": True})
|
||||
Reference in New Issue
Block a user