Files
Faktenkompass/claims/admin.py
T
Pirmin Hinderling (fedora) 28aab3b16a Remove ambiguous evidence level field from claims and the AI assistant.
Drops Evidenzlevel from models, admin, public UI, and draft workflow while keeping Nachweise file uploads unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 22:11:47 +02:00

233 lines
7.9 KiB
Python

from django.contrib import admin
from django.urls import path
from django.utils.html import format_html
from .admin_forms import (
ClaimAdminForm,
CounterArgumentInlineForm,
EvidenceFileInlineForm,
SourceInlineForm,
)
from .admin_views import AssistantView, ReviewDraftView
from .models import Category, Claim, ClaimDraft, CounterArgument, EvidenceFile, Source, Tag
class FaktenAdminMixin:
class Media:
css = {'all': ['css/admin_custom.css']}
js = ['js/admin_custom.js']
class EvidenceFileInline(FaktenAdminMixin, admin.StackedInline):
model = EvidenceFile
form = EvidenceFileInlineForm
extra = 1
fields = ('preview', 'file', 'title', 'caption', 'order')
readonly_fields = ('preview',)
classes = ('collapse-open',)
verbose_name = 'Nachweis'
verbose_name_plural = 'Nachweise (Grafiken / PDFs)'
@admin.display(description='Vorschau')
def preview(self, obj):
if not obj or not obj.pk or not obj.file:
return format_html('<span class="quiet">Nach dem Speichern sichtbar</span>')
if obj.is_image:
return format_html(
'<div class="fk-evidence-preview"><a href="{}" target="_blank" rel="noopener">'
'<img src="{}" alt="{}"></a></div>',
obj.file.url,
obj.file.url,
obj.image_alt,
)
if obj.is_pdf:
return format_html(
'<div class="fk-evidence-preview"><a href="{}" target="_blank" rel="noopener">'
'📄 PDF öffnen</a></div>',
obj.file.url,
)
return ''
class SourceInline(FaktenAdminMixin, admin.StackedInline):
model = Source
form = SourceInlineForm
extra = 1
fields = ('title', 'organization', 'url', 'description')
classes = ('collapse-open',)
verbose_name = 'Quelle'
verbose_name_plural = 'Quellen'
class CounterArgumentInline(FaktenAdminMixin, admin.StackedInline):
model = CounterArgument
form = CounterArgumentInlineForm
extra = 1
fields = ('argument', 'response')
classes = ('collapse-open',)
verbose_name = 'Gegenargument'
verbose_name_plural = 'Gegenargumente'
@admin.register(Tag)
class TagAdmin(FaktenAdminMixin, admin.ModelAdmin):
list_display = ('name', 'slug')
prepopulated_fields = {'slug': ('name',)}
search_fields = ('name',)
@admin.register(Category)
class CategoryAdmin(FaktenAdminMixin, admin.ModelAdmin):
list_display = ('name', 'slug', 'claim_count', 'created_at')
prepopulated_fields = {'slug': ('name',)}
search_fields = ('name', 'description')
fieldsets = (
(None, {
'fields': ('name', 'slug', 'icon'),
}),
('Beschreibung', {
'fields': ('description',),
'classes': ('wide',),
}),
)
def formfield_for_dbfield(self, db_field, request, **kwargs):
formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
if db_field.name == 'description':
formfield.widget.attrs.update({'rows': 4, 'class': 'fk-admin-textarea'})
return formfield
@admin.display(description='Anzahl Behauptungen')
def claim_count(self, obj):
return obj.claims.count()
@admin.register(Claim)
class ClaimAdmin(FaktenAdminMixin, admin.ModelAdmin):
form = ClaimAdminForm
change_form_template = 'admin/claims/claim/change_form.html'
list_display = ('title', 'category', 'status', 'updated_at')
list_display_links = ('title',)
list_filter = ('category', 'status', 'tags')
search_fields = ('title', 'short_answer', 'detailed_explanation')
prepopulated_fields = {'slug': ('title',)}
filter_horizontal = ('tags',)
autocomplete_fields = ('category',)
save_on_top = True
date_hierarchy = 'updated_at'
inlines = [EvidenceFileInline, SourceInline, CounterArgumentInline]
readonly_fields = ('public_link', 'created_at', 'updated_at')
fieldsets = (
('Einordnung', {
'fields': ('category', 'status', 'tags'),
}),
('Behauptung', {
'fields': ('title', 'slug', 'public_link'),
}),
('Texte', {
'fields': ('short_answer', 'detailed_explanation'),
'classes': ('wide',),
'description': 'Kurzantwort: in ~20 Sekunden lesbar. Ausführliche Erklärung optional.',
}),
('Zeitstempel', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',),
}),
)
@admin.display(description='Website')
def public_link(self, obj):
if not obj.pk:
return ''
return format_html(
'<a href="{}" target="_blank" rel="noopener">Behauptung auf der Website öffnen</a>',
obj.get_absolute_url(),
)
@admin.register(EvidenceFile)
class EvidenceFileAdmin(FaktenAdminMixin, admin.ModelAdmin):
list_display = ('preview_thumb', 'display_title', 'claim', 'order', 'created_at')
list_filter = ('claim__category',)
search_fields = ('title', 'caption', 'claim__title')
readonly_fields = ('preview',)
fields = ('claim', 'preview', 'file', 'title', 'caption', 'order')
@admin.display(description='')
def preview_thumb(self, obj):
if obj.is_image and obj.file:
return format_html(
'<img src="{}" style="max-width:48px;max-height:48px;object-fit:contain">',
obj.file.url,
)
return obj.file_extension.upper()
@admin.display(description='Vorschau')
def preview(self, obj):
return EvidenceFileInline.preview(self, obj)
@admin.register(Source)
class SourceAdmin(FaktenAdminMixin, admin.ModelAdmin):
form = SourceInlineForm
list_display = ('title', 'organization', 'claim')
list_filter = ('claim__category',)
search_fields = ('title', 'organization', 'description')
@admin.register(CounterArgument)
class CounterArgumentAdmin(FaktenAdminMixin, admin.ModelAdmin):
form = CounterArgumentInlineForm
list_display = ('argument_preview', 'claim')
list_filter = ('claim__category',)
search_fields = ('argument', 'response')
@admin.display(description='Gegenargument')
def argument_preview(self, obj):
return obj.argument[:80]
@admin.register(ClaimDraft)
class ClaimDraftAdmin(FaktenAdminMixin, admin.ModelAdmin):
change_list_template = 'admin/claims/claimdraft/change_list.html'
list_display = ('title', 'status', 'created_by', 'created_at', 'published_claim')
list_filter = ('status', 'created_at')
search_fields = ('title', 'user_notes', 'article_url')
readonly_fields = (
'created_by', 'status', 'article_url', 'user_notes', 'uploaded_file',
'article_text', 'conversation', 'ai_raw_response', 'error_message',
'published_claim', 'created_at', 'updated_at',
)
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(created_by=request.user)
def has_change_permission(self, request, obj=None):
if obj and not request.user.is_superuser and obj.created_by_id != request.user.id:
return False
return super().has_change_permission(request, obj)
def has_delete_permission(self, request, obj=None):
if obj and not request.user.is_superuser and obj.created_by_id != request.user.id:
return False
return super().has_delete_permission(request, obj)
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
'assistant/',
self.admin_site.admin_view(AssistantView.as_view()),
name='claims_claimdraft_assistant',
),
path(
'<int:draft_id>/review/',
self.admin_site.admin_view(ReviewDraftView.as_view()),
name='claims_claimdraft_review',
),
]
return custom_urls + urls