initial commit

This commit is contained in:
Pirmin Hinderling (fedora)
2026-07-07 13:02:49 +02:00
commit 2c7fb7d030
30 changed files with 1666 additions and 0 deletions
+231
View File
@@ -0,0 +1,231 @@
from django.core.validators import FileExtensionValidator
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
EVIDENCE_FILE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'pdf']
IMAGE_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'}
class Tag(models.Model):
name = models.CharField('Name', max_length=100, unique=True)
slug = models.SlugField('Slug', max_length=120, unique=True, blank=True)
class Meta:
verbose_name = 'Schlagwort'
verbose_name_plural = 'Schlagwörter'
ordering = ['name']
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
class Category(models.Model):
name = models.CharField('Name', max_length=200)
slug = models.SlugField('Slug', max_length=220, unique=True, blank=True)
description = models.TextField('Beschreibung')
icon = models.CharField(
'Icon (Bootstrap Icons Klasse)',
max_length=50,
blank=True,
help_text='z. B. bi-thermometer-half',
)
created_at = models.DateTimeField('Erstellt am', auto_now_add=True)
class Meta:
verbose_name = 'Kategorie'
verbose_name_plural = 'Kategorien'
ordering = ['name']
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('claims:category_detail', kwargs={'slug': self.slug})
@property
def claim_count(self):
return self.claims.count()
class Claim(models.Model):
class Status(models.TextChoices):
FALSCH = 'falsch', 'Falsch'
IRREFUEHREND = 'irrefuehrend', 'Irreführend'
TEILWEISE_RICHTIG = 'teilweise_richtig', 'Teilweise richtig'
UNBELEGT = 'unbelegt', 'Unbelegt'
WISSENSCHAFTLICHER_KONSENS = 'wissenschaftlicher_konsens', 'Wissenschaftlicher Konsens'
OFFENE_FRAGE = 'offene_frage', 'Offene Frage'
class EvidenceLevel(models.TextChoices):
SEHR_STARK = 'sehr_stark', 'Sehr stark'
STARK = 'stark', 'Stark'
MITTEL = 'mittel', 'Mittel'
SCHWACH = 'schwach', 'Schwach'
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='claims',
verbose_name='Kategorie',
)
title = models.CharField('Titel (Behauptung)', max_length=300)
slug = models.SlugField('Slug', max_length=320, unique=True, blank=True)
status = models.CharField(
'Status',
max_length=40,
choices=Status.choices,
default=Status.UNBELEGT,
)
short_answer = models.TextField('Kurzantwort')
detailed_explanation = models.TextField('Ausführliche Erklärung', blank=True)
evidence_level = models.CharField(
'Evidenzlevel',
max_length=20,
choices=EvidenceLevel.choices,
default=EvidenceLevel.MITTEL,
)
tags = models.ManyToManyField(Tag, blank=True, verbose_name='Schlagwörter')
created_at = models.DateTimeField('Erstellt am', auto_now_add=True)
updated_at = models.DateTimeField('Aktualisiert am', auto_now=True)
class Meta:
verbose_name = 'Behauptung'
verbose_name_plural = 'Behauptungen'
ordering = ['-updated_at']
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
base_slug = slugify(self.title)
slug = base_slug
counter = 1
while Claim.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f'{base_slug}-{counter}'
counter += 1
self.slug = slug
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('claims:claim_detail', kwargs={'slug': self.slug})
def get_status_badge_class(self):
mapping = {
self.Status.FALSCH: 'danger',
self.Status.IRREFUEHREND: 'warning',
self.Status.TEILWEISE_RICHTIG: 'info',
self.Status.UNBELEGT: 'secondary',
self.Status.WISSENSCHAFTLICHER_KONSENS: 'success',
self.Status.OFFENE_FRAGE: 'primary',
}
return mapping.get(self.status, 'secondary')
def get_evidence_badge_class(self):
mapping = {
self.EvidenceLevel.SEHR_STARK: 'success',
self.EvidenceLevel.STARK: 'primary',
self.EvidenceLevel.MITTEL: 'info',
self.EvidenceLevel.SCHWACH: 'warning',
}
return mapping.get(self.evidence_level, 'secondary')
def evidence_upload_path(instance, filename):
return f'evidence/{instance.claim.slug}/{filename}'
class EvidenceFile(models.Model):
claim = models.ForeignKey(
Claim,
on_delete=models.CASCADE,
related_name='evidence_files',
verbose_name='Behauptung',
)
title = models.CharField('Titel', max_length=200, blank=True)
file = models.FileField(
'Datei',
upload_to=evidence_upload_path,
validators=[FileExtensionValidator(allowed_extensions=EVIDENCE_FILE_EXTENSIONS)],
help_text='Bilder (JPG, PNG, GIF, WebP, SVG) oder PDF',
)
caption = models.CharField('Bildunterschrift', max_length=500, blank=True)
order = models.PositiveSmallIntegerField('Reihenfolge', default=0)
created_at = models.DateTimeField('Hochgeladen am', auto_now_add=True)
class Meta:
verbose_name = 'Nachweis'
verbose_name_plural = 'Nachweise'
ordering = ['order', 'id']
def __str__(self):
return self.display_title
@property
def display_title(self):
if self.title:
return self.title
return self.file.name.rsplit('/', 1)[-1]
@property
def file_extension(self):
return self.file.name.rsplit('.', 1)[-1].lower()
@property
def is_image(self):
return self.file_extension in IMAGE_EXTENSIONS
@property
def is_pdf(self):
return self.file_extension == 'pdf'
class Source(models.Model):
claim = models.ForeignKey(
Claim,
on_delete=models.CASCADE,
related_name='sources',
verbose_name='Behauptung',
)
title = models.CharField('Titel', max_length=300)
organization = models.CharField('Autor/Organisation', max_length=200)
url = models.URLField('Link', max_length=500)
description = models.TextField('Beschreibung', blank=True)
class Meta:
verbose_name = 'Quelle'
verbose_name_plural = 'Quellen'
ordering = ['id']
def __str__(self):
return self.title
class CounterArgument(models.Model):
claim = models.ForeignKey(
Claim,
on_delete=models.CASCADE,
related_name='counter_arguments',
verbose_name='Behauptung',
)
argument = models.TextField('Gegenargument')
response = models.TextField('Antwort')
class Meta:
verbose_name = 'Gegenargument'
verbose_name_plural = 'Gegenargumente'
ordering = ['id']
def __str__(self):
return self.argument[:80]