Faktenkompass: UI-Rebrand, Admin-Assistent und Produktions-Setup.

Modernisiert die öffentliche Oberfläche mit Inter, Dark Mode und Live-Suche, ergänzt KI-gestützte Entwürfe im Admin mit Nachweis-Uploads und dokumentiert Daphne/Nginx für den Serverbetrieb.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Pirmin Hinderling (fedora)
2026-07-07 15:33:58 +02:00
parent 2c7fb7d030
commit db3558872e
43 changed files with 3014 additions and 208 deletions
+159
View File
@@ -0,0 +1,159 @@
import json
from django import forms
from claims.models import Category, Claim, ClaimDraft
class AssistantInputForm(forms.Form):
article_url = forms.URLField(
label='Artikel-Link',
required=False,
widget=forms.URLInput(attrs={
'class': 'form-control',
'placeholder': 'https://beispiel.de/artikel',
}),
)
user_notes = forms.CharField(
label='Beschreibung / Behauptung',
required=False,
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Welche Aussage soll überprüft werden? Kontext, Stichpunkte …',
}),
)
uploaded_file = forms.FileField(
label='Bild oder PDF',
required=False,
widget=forms.ClearableFileInput(attrs={'class': 'form-control', 'accept': '.jpg,.jpeg,.png,.gif,.webp,.svg,.pdf'}),
)
def clean(self):
cleaned = super().clean()
url = cleaned.get('article_url')
notes = (cleaned.get('user_notes') or '').strip()
uploaded = cleaned.get('uploaded_file')
if not url and not notes and not uploaded:
raise forms.ValidationError(
'Bitte mindestens einen Artikel-Link, eine Beschreibung oder eine Datei angeben.'
)
return cleaned
class ReviewDraftForm(forms.ModelForm):
tags_text = forms.CharField(
label='Schlagwörter',
required=False,
help_text='Kommagetrennt',
widget=forms.TextInput(attrs={'class': 'form-control'}),
)
sources_json = forms.CharField(
label='Quellen (JSON)',
required=False,
widget=forms.Textarea(attrs={'class': 'form-control font-monospace', 'rows': 6}),
)
counter_arguments_json = forms.CharField(
label='Gegenargumente (JSON)',
required=False,
widget=forms.Textarea(attrs={'class': 'form-control font-monospace', 'rows': 6}),
)
category_choice = forms.ModelChoiceField(
label='Bestehende Kategorie',
queryset=Category.objects.all(),
required=False,
empty_label='— Neue Kategorie verwenden —',
widget=forms.Select(attrs={'class': 'form-select'}),
)
class Meta:
model = ClaimDraft
fields = [
'title',
'claim_status',
'short_answer',
'detailed_explanation',
'evidence_level',
'new_category_name',
'new_category_description',
'new_category_icon',
]
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'claim_status': forms.Select(attrs={'class': 'form-select'}),
'short_answer': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
'detailed_explanation': forms.Textarea(attrs={'class': 'form-control', 'rows': 6}),
'evidence_level': forms.Select(attrs={'class': 'form-select'}),
'new_category_name': forms.TextInput(attrs={'class': 'form-control'}),
'new_category_description': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
'new_category_icon': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'bi-book'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get('instance') or getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['tags_text'].initial = ', '.join(instance.tags or [])
self.fields['sources_json'].initial = json.dumps(
instance.sources or [], ensure_ascii=False, indent=2
)
self.fields['counter_arguments_json'].initial = json.dumps(
instance.counter_arguments or [], ensure_ascii=False, indent=2
)
if instance.category_slug:
category = Category.objects.filter(slug=instance.category_slug).first()
if category:
self.fields['category_choice'].initial = category
def clean_tags_text(self):
raw = self.cleaned_data.get('tags_text', '')
return [part.strip() for part in raw.split(',') if part.strip()]
def clean_sources_json(self):
raw = (self.cleaned_data.get('sources_json') or '').strip()
if not raw:
return []
try:
data = json.loads(raw)
except json.JSONDecodeError as exc:
raise forms.ValidationError('Ungültiges JSON für Quellen.') from exc
if not isinstance(data, list):
raise forms.ValidationError('Quellen müssen eine JSON-Liste sein.')
return data
def clean_counter_arguments_json(self):
raw = (self.cleaned_data.get('counter_arguments_json') or '').strip()
if not raw:
return []
try:
data = json.loads(raw)
except json.JSONDecodeError as exc:
raise forms.ValidationError('Ungültiges JSON für Gegenargumente.') from exc
if not isinstance(data, list):
raise forms.ValidationError('Gegenargumente müssen eine JSON-Liste sein.')
return data
def save(self, commit=True):
draft = super().save(commit=False)
draft.tags = self.cleaned_data['tags_text']
draft.sources = self.cleaned_data['sources_json']
draft.counter_arguments = self.cleaned_data['counter_arguments_json']
category = self.cleaned_data.get('category_choice')
if category:
draft.category_slug = category.slug
else:
draft.category_slug = ''
if commit:
draft.save()
return draft
class RefineDraftForm(forms.Form):
refinement = forms.CharField(
label='Überarbeitungswunsch',
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'z. B. „Kurzantwort kürzer formulieren“ oder „mehr Quellen hinzufügen“',
}),
)