This commit is contained in:
Iliyan Angelov
2025-11-26 22:32:20 +02:00
commit ed94dd22dd
150 changed files with 14058 additions and 0 deletions

210
legal/models.py Normal file
View File

@@ -0,0 +1,210 @@
"""
Legal compliance and GDPR models.
"""
from django.db import models
from django.contrib.auth import get_user_model
from django.utils import timezone
User = get_user_model()
class ConsentRecord(models.Model):
"""
Track user consent for GDPR compliance.
"""
CONSENT_TYPE_CHOICES = [
('privacy_policy', 'Privacy Policy'),
('terms_of_service', 'Terms of Service'),
('data_processing', 'Data Processing'),
('marketing', 'Marketing Communications'),
('cookies', 'Cookie Consent'),
]
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='consents',
null=True,
blank=True
)
consent_type = models.CharField(
max_length=50,
choices=CONSENT_TYPE_CHOICES
)
consent_given = models.BooleanField(default=False)
ip_address = models.GenericIPAddressField(null=True, blank=True)
user_agent = models.TextField(blank=True)
timestamp = models.DateTimeField(auto_now_add=True)
version = models.CharField(
max_length=20,
blank=True,
help_text='Version of the policy/terms'
)
class Meta:
db_table = 'legal_consentrecord'
verbose_name = 'Consent Record'
verbose_name_plural = 'Consent Records'
ordering = ['-timestamp']
indexes = [
models.Index(fields=['user', 'consent_type']),
models.Index(fields=['consent_type', 'timestamp']),
]
def __str__(self):
status = "Given" if self.consent_given else "Not Given"
return f"{self.get_consent_type_display()} - {status} - {self.timestamp}"
class DataRequest(models.Model):
"""
GDPR data subject requests (access, deletion, portability).
"""
REQUEST_TYPE_CHOICES = [
('access', 'Data Access Request'),
('deletion', 'Data Deletion Request'),
('portability', 'Data Portability Request'),
('rectification', 'Data Rectification Request'),
('objection', 'Objection to Processing'),
('restriction', 'Restriction of Processing'),
]
STATUS_CHOICES = [
('pending', 'Pending'),
('in_progress', 'In Progress'),
('completed', 'Completed'),
('rejected', 'Rejected'),
]
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='data_requests'
)
request_type = models.CharField(
max_length=50,
choices=REQUEST_TYPE_CHOICES
)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='pending'
)
description = models.TextField(
blank=True,
help_text='Additional details about the request'
)
requested_at = models.DateTimeField(auto_now_add=True)
completed_at = models.DateTimeField(null=True, blank=True)
response_data = models.JSONField(
default=dict,
blank=True,
help_text='Response data (e.g., exported data)'
)
response_file = models.FileField(
upload_to='data_requests/',
blank=True,
null=True,
help_text='File containing requested data'
)
handled_by = models.ForeignKey(
User,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='handled_data_requests',
limit_choices_to={'role': 'admin'}
)
notes = models.TextField(
blank=True,
help_text='Internal notes about handling the request'
)
class Meta:
db_table = 'legal_datarequest'
verbose_name = 'Data Request'
verbose_name_plural = 'Data Requests'
ordering = ['-requested_at']
indexes = [
models.Index(fields=['user', 'status']),
models.Index(fields=['request_type', 'status']),
models.Index(fields=['status', 'requested_at']),
]
def __str__(self):
return f"{self.get_request_type_display()} by {self.user.username} - {self.get_status_display()}"
class SecurityEvent(models.Model):
"""
Security event logging for compliance and monitoring.
"""
EVENT_TYPE_CHOICES = [
('login_success', 'Successful Login'),
('login_failed', 'Failed Login'),
('password_change', 'Password Changed'),
('account_locked', 'Account Locked'),
('suspicious_activity', 'Suspicious Activity'),
('data_breach', 'Data Breach'),
('unauthorized_access', 'Unauthorized Access Attempt'),
('file_upload', 'File Upload'),
('data_export', 'Data Export'),
('admin_action', 'Admin Action'),
]
SEVERITY_CHOICES = [
('low', 'Low'),
('medium', 'Medium'),
('high', 'High'),
('critical', 'Critical'),
]
event_type = models.CharField(
max_length=50,
choices=EVENT_TYPE_CHOICES
)
user = models.ForeignKey(
User,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='security_events'
)
ip_address = models.GenericIPAddressField(null=True, blank=True)
user_agent = models.TextField(blank=True)
details = models.JSONField(
default=dict,
blank=True,
help_text='Additional event details'
)
severity = models.CharField(
max_length=20,
choices=SEVERITY_CHOICES,
default='low'
)
timestamp = models.DateTimeField(auto_now_add=True)
resolved = models.BooleanField(default=False)
resolved_at = models.DateTimeField(null=True, blank=True)
resolved_by = models.ForeignKey(
User,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='resolved_security_events',
limit_choices_to={'role': 'admin'}
)
class Meta:
db_table = 'security_securityevent'
verbose_name = 'Security Event'
verbose_name_plural = 'Security Events'
ordering = ['-timestamp']
indexes = [
models.Index(fields=['event_type', 'timestamp']),
models.Index(fields=['severity', 'timestamp']),
models.Index(fields=['user', 'timestamp']),
models.Index(fields=['resolved', 'timestamp']),
]
def __str__(self):
return f"{self.get_event_type_display()} - {self.get_severity_display()} - {self.timestamp}"