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:
+159
@@ -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“',
|
||||
}),
|
||||
)
|
||||
Reference in New Issue
Block a user