update
This commit is contained in:
411
reports/models.py
Normal file
411
reports/models.py
Normal file
@@ -0,0 +1,411 @@
|
||||
"""
|
||||
Scam and fraud report models.
|
||||
"""
|
||||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from django.core.cache import cache
|
||||
from accounts.security import DataEncryption
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class SiteSettings(models.Model):
|
||||
"""
|
||||
Site-wide settings that can be managed from admin.
|
||||
Uses singleton pattern - only one instance should exist.
|
||||
"""
|
||||
contact_email = models.EmailField(
|
||||
default='support@fraudplatform.bg',
|
||||
help_text='Основен имейл за контакти и поддръжка'
|
||||
)
|
||||
contact_phone = models.CharField(
|
||||
max_length=50,
|
||||
blank=True,
|
||||
default='',
|
||||
help_text='Телефонен номер за контакти (незадължително)'
|
||||
)
|
||||
contact_address = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
default='',
|
||||
help_text='Адрес за контакти (незадължително)'
|
||||
)
|
||||
|
||||
# Email Server Settings
|
||||
email_backend = models.CharField(
|
||||
max_length=100,
|
||||
default='django.core.mail.backends.smtp.EmailBackend',
|
||||
choices=[
|
||||
('django.core.mail.backends.smtp.EmailBackend', 'SMTP'),
|
||||
('django.core.mail.backends.console.EmailBackend', 'Console (Development)'),
|
||||
('django.core.mail.backends.filebased.EmailBackend', 'File Based'),
|
||||
],
|
||||
help_text='Тип на имейл сървъра'
|
||||
)
|
||||
email_host = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
default='',
|
||||
help_text='SMTP сървър (напр. smtp.gmail.com)'
|
||||
)
|
||||
email_port = models.IntegerField(
|
||||
default=587,
|
||||
help_text='SMTP порт (обикновено 587 за TLS или 465 за SSL)'
|
||||
)
|
||||
email_use_tls = models.BooleanField(
|
||||
default=True,
|
||||
help_text='Използване на TLS (за порт 587)'
|
||||
)
|
||||
email_use_ssl = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Използване на SSL (за порт 465)'
|
||||
)
|
||||
email_host_user = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
default='',
|
||||
help_text='SMTP потребителско име / имейл'
|
||||
)
|
||||
email_host_password = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
default='',
|
||||
help_text='SMTP парола (ще бъде криптирана)'
|
||||
)
|
||||
default_from_email = models.EmailField(
|
||||
default='noreply@fraudplatform.bg',
|
||||
help_text='Имейл адрес по подразбиране за изпращане'
|
||||
)
|
||||
email_timeout = models.IntegerField(
|
||||
default=10,
|
||||
help_text='Таймаут за имейл връзка (секунди)'
|
||||
)
|
||||
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Настройки на Сайта'
|
||||
verbose_name_plural = 'Настройки на Сайта'
|
||||
db_table = 'reports_sitesettings'
|
||||
|
||||
def __str__(self):
|
||||
return 'Настройки на Сайта'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Ensure only one instance exists
|
||||
self.pk = 1
|
||||
|
||||
# Encrypt email password if it's provided and not already encrypted
|
||||
if self.email_host_password:
|
||||
# Check if it's already encrypted by trying to decrypt it
|
||||
# If decryption succeeds, it's already encrypted, so keep original
|
||||
# If decryption fails, it's plain text, so encrypt it
|
||||
is_encrypted = False
|
||||
try:
|
||||
# Try to decrypt - if it succeeds, it's already encrypted
|
||||
DataEncryption.decrypt(self.email_host_password)
|
||||
is_encrypted = True
|
||||
except (Exception, ValueError, TypeError):
|
||||
# Decryption failed, so it's plain text
|
||||
is_encrypted = False
|
||||
|
||||
# Only encrypt if it's not already encrypted
|
||||
if not is_encrypted:
|
||||
self.email_host_password = DataEncryption.encrypt(self.email_host_password)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
# Clear cache when settings are updated
|
||||
cache.delete('site_settings')
|
||||
|
||||
def get_email_password(self):
|
||||
"""Get decrypted email password."""
|
||||
if not self.email_host_password:
|
||||
return ''
|
||||
try:
|
||||
return DataEncryption.decrypt(self.email_host_password)
|
||||
except:
|
||||
# If decryption fails, return as-is (might be plain text from migration)
|
||||
return self.email_host_password
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
# Prevent deletion - settings should always exist
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_settings(cls):
|
||||
"""Get site settings with caching."""
|
||||
settings = cache.get('site_settings')
|
||||
if settings is None:
|
||||
settings, created = cls.objects.get_or_create(pk=1)
|
||||
cache.set('site_settings', settings, 3600) # Cache for 1 hour
|
||||
return settings
|
||||
|
||||
|
||||
class ScamTag(models.Model):
|
||||
"""
|
||||
Tags for categorizing scam reports.
|
||||
"""
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
slug = models.SlugField(max_length=100, unique=True, blank=True)
|
||||
description = models.TextField(blank=True)
|
||||
color = models.CharField(
|
||||
max_length=7,
|
||||
default='#007bff',
|
||||
help_text='Hex color code for display'
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'reports_scamtag'
|
||||
verbose_name = 'Scam Tag'
|
||||
verbose_name_plural = 'Scam Tags'
|
||||
ordering = ['name']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ScamReport(models.Model):
|
||||
"""
|
||||
Main scam/fraud report model.
|
||||
"""
|
||||
SCAM_TYPE_CHOICES = [
|
||||
('phishing', 'Phishing'),
|
||||
('fake_website', 'Fake Website'),
|
||||
('romance_scam', 'Romance Scam'),
|
||||
('investment_scam', 'Investment Scam'),
|
||||
('tech_support_scam', 'Tech Support Scam'),
|
||||
('identity_theft', 'Identity Theft'),
|
||||
('fake_product', 'Fake Product'),
|
||||
('advance_fee', 'Advance Fee Fraud'),
|
||||
('other', 'Other'),
|
||||
]
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('pending', 'Pending Review'),
|
||||
('under_review', 'Under Review'),
|
||||
('verified', 'Verified'),
|
||||
('rejected', 'Rejected'),
|
||||
('archived', 'Archived'),
|
||||
]
|
||||
|
||||
# Reporter information
|
||||
reporter = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='reports'
|
||||
)
|
||||
is_anonymous = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Report submitted anonymously'
|
||||
)
|
||||
|
||||
# Report details
|
||||
title = models.CharField(max_length=200)
|
||||
description = models.TextField()
|
||||
scam_type = models.CharField(
|
||||
max_length=50,
|
||||
choices=SCAM_TYPE_CHOICES,
|
||||
default='other'
|
||||
)
|
||||
|
||||
# Reported entities
|
||||
reported_url = models.URLField(blank=True, null=True, max_length=500)
|
||||
reported_email = models.EmailField(blank=True, null=True)
|
||||
reported_phone = models.CharField(max_length=20, blank=True, null=True)
|
||||
reported_company = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
# Evidence
|
||||
evidence_files = models.JSONField(
|
||||
default=list,
|
||||
blank=True,
|
||||
help_text='List of file paths for evidence'
|
||||
)
|
||||
|
||||
# Status and verification
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='pending'
|
||||
)
|
||||
verification_score = models.IntegerField(
|
||||
default=0,
|
||||
help_text='OSINT verification confidence score (0-100)'
|
||||
)
|
||||
|
||||
# Visibility
|
||||
is_public = models.BooleanField(
|
||||
default=True,
|
||||
help_text='Visible in public database'
|
||||
)
|
||||
is_auto_discovered = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Automatically discovered by OSINT system'
|
||||
)
|
||||
|
||||
# Metadata
|
||||
tags = models.ManyToManyField(ScamTag, blank=True, related_name='reports')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
verified_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
# IP tracking for anonymous reports
|
||||
reporter_ip = models.GenericIPAddressField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'reports_scamreport'
|
||||
verbose_name = 'Scam Report'
|
||||
verbose_name_plural = 'Scam Reports'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['status', 'created_at']),
|
||||
models.Index(fields=['scam_type', 'status']),
|
||||
models.Index(fields=['reported_url']),
|
||||
models.Index(fields=['reported_email']),
|
||||
models.Index(fields=['reported_phone']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title} - {self.get_status_display()}"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('reports:detail', kwargs={'pk': self.pk})
|
||||
|
||||
def get_reporter_display(self):
|
||||
if self.is_anonymous:
|
||||
return "Anonymous"
|
||||
return self.reporter.username if self.reporter else "Unknown"
|
||||
|
||||
|
||||
class ScamVerification(models.Model):
|
||||
"""
|
||||
OSINT verification data for scam reports.
|
||||
"""
|
||||
VERIFICATION_METHOD_CHOICES = [
|
||||
('whois', 'WHOIS Lookup'),
|
||||
('dns', 'DNS Records'),
|
||||
('ssl', 'SSL Certificate'),
|
||||
('archive', 'Wayback Machine'),
|
||||
('email_check', 'Email Validation'),
|
||||
('phone_check', 'Phone Validation'),
|
||||
('business_registry', 'Business Registry'),
|
||||
('social_media', 'Social Media'),
|
||||
('manual', 'Manual Review'),
|
||||
]
|
||||
|
||||
report = models.ForeignKey(
|
||||
ScamReport,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='verifications'
|
||||
)
|
||||
verification_method = models.CharField(
|
||||
max_length=50,
|
||||
choices=VERIFICATION_METHOD_CHOICES
|
||||
)
|
||||
verification_data = models.JSONField(
|
||||
default=dict,
|
||||
help_text='Raw verification data'
|
||||
)
|
||||
confidence_score = models.IntegerField(
|
||||
default=0,
|
||||
help_text='Confidence score for this verification (0-100)'
|
||||
)
|
||||
verified_by = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='verifications'
|
||||
)
|
||||
notes = models.TextField(blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'reports_scamverification'
|
||||
verbose_name = 'Scam Verification'
|
||||
verbose_name_plural = 'Scam Verifications'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f"Verification for {self.report.title} via {self.get_verification_method_display()}"
|
||||
|
||||
|
||||
class TakedownRequest(models.Model):
|
||||
"""
|
||||
Request to take down a scam report by the accused party.
|
||||
"""
|
||||
STATUS_CHOICES = [
|
||||
('pending', 'Pending Review'),
|
||||
('under_review', 'Under Review'),
|
||||
('approved', 'Approved'),
|
||||
('rejected', 'Rejected'),
|
||||
]
|
||||
|
||||
report = models.ForeignKey(
|
||||
ScamReport,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='takedown_requests'
|
||||
)
|
||||
requester_name = models.CharField(
|
||||
max_length=200,
|
||||
help_text='Име на заявителя'
|
||||
)
|
||||
requester_email = models.EmailField(
|
||||
help_text='Имейл на заявителя'
|
||||
)
|
||||
requester_phone = models.CharField(
|
||||
max_length=50,
|
||||
blank=True,
|
||||
help_text='Телефон на заявителя (незадължително)'
|
||||
)
|
||||
reason = models.TextField(
|
||||
help_text='Причина за заявката за премахване'
|
||||
)
|
||||
evidence = models.TextField(
|
||||
blank=True,
|
||||
help_text='Доказателства или допълнителна информация'
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='pending'
|
||||
)
|
||||
reviewed_by = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='reviewed_takedown_requests',
|
||||
limit_choices_to={'role__in': ['moderator', 'admin']}
|
||||
)
|
||||
review_notes = models.TextField(
|
||||
blank=True,
|
||||
help_text='Бележки от модератора'
|
||||
)
|
||||
ip_address = models.GenericIPAddressField(null=True, blank=True)
|
||||
user_agent = models.TextField(blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
reviewed_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'reports_takedownrequest'
|
||||
verbose_name = 'Заявка за Премахване'
|
||||
verbose_name_plural = 'Заявки за Премахване'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['report', 'status']),
|
||||
models.Index(fields=['status', 'created_at']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"Takedown request for {self.report.title} by {self.requester_name}"
|
||||
Reference in New Issue
Block a user