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]