16428f2c16
Models, AI assistant, and UI now show diskussionsfertige Antworten without titles; shared search partial fixes aria-label rendering. Co-authored-by: Cursor <cursoragent@cursor.com>
105 lines
3.2 KiB
Python
105 lines
3.2 KiB
Python
import os
|
|
import uuid
|
|
|
|
from django.db import transaction
|
|
from django.utils.text import slugify
|
|
|
|
from claims.models import Category, Claim, EvidenceFile, PossibleAnswer, Source, Tag
|
|
|
|
|
|
class DraftPublishError(Exception):
|
|
pass
|
|
|
|
|
|
def _resolve_category(draft):
|
|
if draft.category_slug:
|
|
category = Category.objects.filter(slug=draft.category_slug).first()
|
|
if category:
|
|
return category
|
|
|
|
if draft.new_category_name:
|
|
category, _ = Category.objects.get_or_create(
|
|
slug=slugify(draft.new_category_name),
|
|
defaults={
|
|
'name': draft.new_category_name,
|
|
'description': draft.new_category_description or draft.new_category_name,
|
|
'icon': draft.new_category_icon or 'bi-book',
|
|
},
|
|
)
|
|
return category
|
|
|
|
raise DraftPublishError('Bitte eine Kategorie auswählen oder eine neue angeben.')
|
|
|
|
|
|
def _get_or_create_tags(tag_names):
|
|
tags = []
|
|
for name in tag_names or []:
|
|
clean = str(name).strip()
|
|
if not clean:
|
|
continue
|
|
tag, _ = Tag.objects.get_or_create(name=clean, defaults={'slug': slugify(clean)})
|
|
tags.append(tag)
|
|
return tags
|
|
|
|
|
|
@transaction.atomic
|
|
def publish_draft(draft):
|
|
if draft.status == draft.DraftStatus.PUBLISHED and draft.published_claim_id:
|
|
return draft.published_claim
|
|
|
|
if not draft.title or not draft.short_answer:
|
|
raise DraftPublishError('Titel und Kurzantwort sind erforderlich.')
|
|
|
|
category = _resolve_category(draft)
|
|
claim = Claim.objects.create(
|
|
category=category,
|
|
title=draft.title,
|
|
status=draft.claim_status or Claim.Status.UNBELEGT,
|
|
short_answer=draft.short_answer,
|
|
detailed_explanation=draft.detailed_explanation,
|
|
)
|
|
claim.tags.set(_get_or_create_tags(draft.tags))
|
|
|
|
for source in draft.sources or []:
|
|
url = (source.get('url') or '').strip()
|
|
if not url:
|
|
continue
|
|
Source.objects.create(
|
|
claim=claim,
|
|
title=source.get('title') or url,
|
|
organization=source.get('organization') or 'Unbekannt',
|
|
url=url,
|
|
description=source.get('description', ''),
|
|
)
|
|
|
|
for item in draft.possible_answers or []:
|
|
if isinstance(item, str):
|
|
answer = item.strip()
|
|
else:
|
|
answer = (item.get('answer') or item.get('response') or '').strip()
|
|
if answer:
|
|
PossibleAnswer.objects.create(claim=claim, answer=answer)
|
|
|
|
if draft.uploaded_file:
|
|
_attach_uploaded_file(draft, claim)
|
|
|
|
draft.published_claim = claim
|
|
draft.status = draft.DraftStatus.PUBLISHED
|
|
draft.save(update_fields=['published_claim', 'status', 'updated_at'])
|
|
return claim
|
|
|
|
|
|
def _attach_uploaded_file(draft, claim):
|
|
if not draft.uploaded_file:
|
|
return
|
|
|
|
original_name = os.path.basename(draft.uploaded_file.name)
|
|
ext = original_name.rsplit('.', 1)[-1].lower() if '.' in original_name else ''
|
|
if ext not in {'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'pdf'}:
|
|
return
|
|
|
|
evidence = EvidenceFile(claim=claim, title='')
|
|
short_name = f'{uuid.uuid4().hex[:12]}.{ext}'
|
|
with draft.uploaded_file.open('rb') as source_file:
|
|
evidence.file.save(short_name, source_file, save=True)
|