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

247
reports/admin.py Normal file
View File

@@ -0,0 +1,247 @@
"""
Admin configuration for reports app.
"""
from django.contrib import admin
from django.utils.html import format_html
from django import forms
from .models import ScamTag, ScamReport, ScamVerification, SiteSettings, TakedownRequest
@admin.register(ScamTag)
class ScamTagAdmin(admin.ModelAdmin):
"""Scam tag admin."""
list_display = ('name', 'slug', 'color')
prepopulated_fields = {'slug': ('name',)}
search_fields = ('name',)
@admin.register(ScamReport)
class ScamReportAdmin(admin.ModelAdmin):
"""Scam report admin."""
list_display = ('title', 'reporter', 'scam_type', 'status', 'verification_score', 'created_at')
list_filter = ('status', 'scam_type', 'is_public', 'created_at')
search_fields = ('title', 'description', 'reported_url', 'reported_email', 'reported_phone')
readonly_fields = ('created_at', 'updated_at', 'verified_at', 'reporter_ip')
filter_horizontal = ('tags',)
date_hierarchy = 'created_at'
fieldsets = (
('Report Information', {
'fields': ('title', 'description', 'scam_type', 'tags')
}),
('Reported Entities', {
'fields': ('reported_url', 'reported_email', 'reported_phone', 'reported_company')
}),
('Reporter', {
'fields': ('reporter', 'is_anonymous', 'reporter_ip')
}),
('Status', {
'fields': ('status', 'verification_score', 'is_public', 'verified_at')
}),
('Evidence', {
'fields': ('evidence_files',)
}),
('Timestamps', {
'fields': ('created_at', 'updated_at')
}),
)
@admin.register(ScamVerification)
class ScamVerificationAdmin(admin.ModelAdmin):
"""Scam verification admin."""
list_display = ('report', 'verification_method', 'confidence_score', 'verified_by', 'created_at')
list_filter = ('verification_method', 'created_at')
search_fields = ('report__title', 'notes')
readonly_fields = ('created_at',)
@admin.register(SiteSettings)
class SiteSettingsAdmin(admin.ModelAdmin):
"""Site settings admin - singleton pattern."""
def has_add_permission(self, request):
# Only allow one instance
return not SiteSettings.objects.exists()
def has_delete_permission(self, request, obj=None):
# Prevent deletion
return False
fieldsets = (
('Контактна Информация', {
'fields': ('contact_email', 'contact_phone', 'contact_address'),
'description': 'Тези настройки се използват навсякъде в сайта - в подножието, страницата за контакти, структурираните данни и др.'
}),
('Настройки на Имейл Сървър', {
'fields': (
'email_backend',
'email_host',
'email_port',
'email_use_tls',
'email_use_ssl',
'email_host_user',
'email_host_password',
'default_from_email',
'email_timeout',
),
'description': 'Настройки за SMTP сървър. Използват се за всички имейли в платформата - контактни форми, нулиране на пароли, уведомления и др. Паролата се криптира автоматично.'
}),
('Информация', {
'fields': ('updated_at',),
'classes': ('collapse',)
}),
)
readonly_fields = ('updated_at',)
def get_form(self, request, obj=None, **kwargs):
"""Customize form to handle password field."""
form = super().get_form(request, obj, **kwargs)
# Make password field a password input
form.base_fields['email_host_password'].widget = forms.PasswordInput(attrs={
'class': 'vTextField',
'autocomplete': 'new-password'
})
# Add help text
form.base_fields['email_host_password'].help_text = 'Въведете нова парола или оставете празно, за да запазите текущата.'
return form
def save_model(self, request, obj, form, change):
"""Handle password encryption and clear cache."""
# If password field is empty and we're editing, keep the old password
if change and not form.cleaned_data.get('email_host_password'):
old_obj = self.model.objects.get(pk=obj.pk)
obj.email_host_password = old_obj.email_host_password
# Validate TLS/SSL are mutually exclusive
if obj.email_use_tls and obj.email_use_ssl:
from django.contrib import messages
messages.warning(request, 'TLS и SSL не могат да бъдат активирани едновременно. SSL е деактивиран, използва се TLS.')
obj.email_use_ssl = False
# Save will encrypt the password if it's provided
super().save_model(request, obj, form, change)
# Clear email backend cache to reload settings
from django.core.cache import cache
cache.delete('site_settings')
def changelist_view(self, request, extra_context=None):
# Redirect to the single instance if it exists
from django.shortcuts import redirect
from django.urls import reverse
if SiteSettings.objects.exists():
obj = SiteSettings.objects.get(pk=1)
url = reverse('admin:reports_sitesettings_change', args=[str(obj.pk)])
return redirect(url)
return super().changelist_view(request, extra_context)
def response_change(self, request, obj):
"""Handle test email button."""
if "_test_email" in request.POST:
try:
from django.core.mail import send_mail
from django.contrib import messages
test_email = request.POST.get('test_email_address', request.user.email)
if not test_email:
messages.error(request, 'Моля, въведете имейл адрес за тест.')
return super().response_change(request, obj)
# Check if SMTP is configured
if obj.email_backend == 'django.core.mail.backends.smtp.EmailBackend' and not obj.email_host:
messages.warning(request, 'SMTP сървърът не е конфигуриран. Моля, въведете Email Host преди изпращане на тестов имейл.')
return super().response_change(request, obj)
# Get the connection to check backend type
from django.core.mail import get_connection, EmailMessage
connection = get_connection()
backend_name = connection.__class__.__name__
import logging
logger = logging.getLogger(__name__)
logger.info(f"Using email backend: {backend_name}")
# Check underlying backend
if hasattr(connection, '_backend') and connection._backend:
underlying_backend = connection._backend.__class__.__name__
logger.info(f"Underlying backend: {underlying_backend}")
# Send email using EmailMessage for better error handling
email = EmailMessage(
subject='Тестов Имейл от Портал за Докладване на Измами',
body='Това е тестов имейл за проверка на настройките на имейл сървъра. Ако получавате този имейл, настройките са правилни.',
from_email=obj.default_from_email,
to=[test_email],
connection=connection,
)
result = email.send(fail_silently=False)
logger.info(f"Email send result: {result} (1 = success, 0 = failed)")
# Check which backend was actually used
if 'Console' in backend_name or 'console' in str(connection.__class__.__module__):
messages.warning(request, f'Имейлът е изпратен чрез конзолен backend (за разработка). За реално изпращане, конфигурирайте SMTP настройките. Backend: {backend_name}')
else:
messages.success(request, f'Тестов имейл изпратен успешно до {test_email}! Използван backend: {backend_name}')
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.exception("Error sending test email")
error_msg = str(e)
if 'authentication failed' in error_msg.lower():
messages.error(request, f'Грешка при удостоверяване: Проверете потребителското име и паролата.')
elif 'connection' in error_msg.lower() or 'timeout' in error_msg.lower():
messages.error(request, f'Грешка при свързване: Проверете SMTP сървъра и порта.')
else:
messages.error(request, f'Грешка при изпращане на тестов имейл: {error_msg}')
return super().response_change(request, obj)
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
if object_id:
obj = self.get_object(request, object_id)
if obj:
extra_context['show_test_email'] = True
return super().changeform_view(request, object_id, form_url, extra_context)
@admin.register(TakedownRequest)
class TakedownRequestAdmin(admin.ModelAdmin):
"""Takedown request admin."""
list_display = ('report', 'requester_name', 'requester_email', 'status', 'created_at', 'reviewed_by')
list_filter = ('status', 'created_at', 'reviewed_at')
search_fields = ('requester_name', 'requester_email', 'report__title', 'reason')
readonly_fields = ('created_at', 'updated_at', 'ip_address', 'user_agent')
date_hierarchy = 'created_at'
fieldsets = (
('Информация за Доклада', {
'fields': ('report',)
}),
('Информация за Заявителя', {
'fields': ('requester_name', 'requester_email', 'requester_phone')
}),
('Детайли на Заявката', {
'fields': ('reason', 'evidence')
}),
('Статус и Преглед', {
'fields': ('status', 'reviewed_by', 'review_notes', 'reviewed_at')
}),
('Техническа Информация', {
'fields': ('ip_address', 'user_agent', 'created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
def save_model(self, request, obj, form, change):
if change and 'status' in form.changed_data and obj.status in ['approved', 'rejected']:
from django.utils import timezone
obj.reviewed_by = request.user
obj.reviewed_at = timezone.now()
super().save_model(request, obj, form, change)