Files
OSINT/reports/views.py
Iliyan Angelov ed94dd22dd update
2025-11-26 22:32:20 +02:00

455 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Views for reports app.
"""
from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic import TemplateView, ListView, DetailView, CreateView, UpdateView, DeleteView, FormView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.db.models import Q, Count
from django.core.exceptions import ValidationError
from accounts.security import InputSanitizer
from .models import ScamReport, ScamTag, TakedownRequest
from .forms import ScamReportForm, ContactForm, TakedownRequestForm
from django.contrib import messages
from django.core.mail import send_mail
from django.conf import settings
# Bulgarian translations for scam types
SCAM_TYPE_BG = {
'phishing': 'Фишинг',
'fake_website': 'Фалшив Уебсайт',
'romance_scam': 'Романтична Измама',
'investment_scam': 'Инвестиционна Измама',
'tech_support_scam': 'Техническа Поддръжка Измама',
'identity_theft': 'Кражба на Личност',
'fake_product': 'Фалшив Продукт',
'advance_fee': 'Авансово Плащане',
'other': 'Друго',
}
class HomeView(TemplateView):
"""Home page view."""
template_name = 'reports/home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['total_reports'] = ScamReport.objects.filter(status='verified').count()
context['recent_reports'] = ScamReport.objects.filter(
is_public=True,
status='verified'
).order_by('-created_at')[:5]
# Get scam types with display names - ALL types, not just top 5
scam_types_data = ScamReport.objects.filter(
status='verified'
).values('scam_type').annotate(
count=Count('id')
).order_by('-count')
# Add display names with Bulgarian translations
scam_types_list = []
total_verified = context['total_reports'] or 1 # Avoid division by zero
for item in scam_types_data:
scam_type_key = item['scam_type']
display_name = SCAM_TYPE_BG.get(scam_type_key, dict(ScamReport.SCAM_TYPE_CHOICES).get(scam_type_key, scam_type_key))
percentage = (item['count'] / total_verified * 100) if total_verified > 0 else 0
scam_types_list.append({
'scam_type': scam_type_key,
'display_name': display_name,
'count': item['count'],
'percentage': round(percentage, 1)
})
context['scam_types'] = scam_types_list
# SEO metadata
self.request.seo_title = 'Портал за Докладване на Измами - България'
self.request.seo_description = f'Портал за докладване на измами. Над {context["total_reports"]} верифицирани доклада. Защита на гражданите от онлайн измами, фишинг, фалшиви уебсайтове и киберпрестъпления.'
self.request.seo_keywords = 'измами, докладване измами, киберпрестъпления, фишинг, фалшив уебсайт, защита потребители, България, портал за докладване на измами, анти-измами'
self.request.canonical_url = self.request.build_absolute_uri('/')
return context
class ReportListView(ListView):
"""List all public verified reports."""
model = ScamReport
template_name = 'reports/list.html'
context_object_name = 'reports'
paginate_by = 20
def get_queryset(self):
return ScamReport.objects.filter(
is_public=True,
status='verified'
).select_related('reporter').prefetch_related('tags')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# SEO metadata
self.request.seo_title = 'Всички Доклади за Измами - Портал за Докладване на Измами'
self.request.seo_description = 'Прегледайте всички верифицирани доклади за измами в България. Търсете по вид измама, дата и ключови думи.'
self.request.seo_keywords = 'доклади измами, списък измами, верифицирани доклади, измами България'
self.request.canonical_url = self.request.build_absolute_uri('/reports/')
return context
class ReportDetailView(DetailView):
"""View a single report."""
model = ScamReport
template_name = 'reports/detail.html'
context_object_name = 'report'
def get_queryset(self):
# Allow viewing if public or user is owner/moderator
queryset = ScamReport.objects.all()
if not self.request.user.is_authenticated:
queryset = queryset.filter(is_public=True, status='verified')
elif not self.request.user.is_moderator():
queryset = queryset.filter(
Q(is_public=True, status='verified') | Q(reporter=self.request.user)
)
return queryset.select_related('reporter').prefetch_related('tags', 'verifications', 'moderation_actions')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
report = context['report']
# SEO metadata
scam_type_display = SCAM_TYPE_BG.get(report.scam_type, report.get_scam_type_display())
self.request.seo_title = f'{report.title} - Доклад за Измама'
self.request.seo_description = f'Доклад за {scam_type_display.lower()}: {report.description[:150]}...' if len(report.description) > 150 else report.description
self.request.seo_keywords = f'измама, {scam_type_display.lower()}, доклад, {", ".join([tag.name for tag in report.tags.all()[:5]])}'
self.request.seo_type = 'article'
self.request.canonical_url = self.request.build_absolute_uri(report.get_absolute_url())
return context
class ReportCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
"""Create a new scam report."""
model = ScamReport
form_class = ScamReportForm
template_name = 'reports/create.html'
success_url = reverse_lazy('reports:my_reports')
success_message = "Report submitted successfully! It will be reviewed by moderators."
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# SEO metadata
self.request.seo_title = 'Докладване на Измама - Портал за Докладване на Измами'
self.request.seo_description = 'Докладвайте измама. Помогнете да защитим другите граждани от онлайн измами и киберпрестъпления.'
self.request.seo_keywords = 'докладване измама, сигнализиране измама, докладване онлайн измама'
self.request.canonical_url = self.request.build_absolute_uri('/create/')
self.request.meta_robots = 'noindex, nofollow' # Don't index form pages
return context
def get_form_kwargs(self):
"""Pass request to form for rate limiting."""
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def form_valid(self, form):
# Sanitize user input
if form.cleaned_data.get('title'):
form.cleaned_data['title'] = InputSanitizer.sanitize_html(form.cleaned_data['title'])
if form.cleaned_data.get('description'):
form.cleaned_data['description'] = InputSanitizer.sanitize_html(form.cleaned_data['description'])
# Validate URLs
if form.cleaned_data.get('reported_url'):
if not InputSanitizer.validate_url(form.cleaned_data['reported_url']):
form.add_error('reported_url', 'Invalid URL format')
return self.form_invalid(form)
# Validate email
if form.cleaned_data.get('reported_email'):
if not InputSanitizer.validate_email(form.cleaned_data['reported_email']):
form.add_error('reported_email', 'Invalid email format')
return self.form_invalid(form)
form.instance.reporter = self.request.user
form.instance.reporter_ip = self.get_client_ip()
response = super().form_valid(form)
return response
def get_client_ip(self):
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = self.request.META.get('REMOTE_ADDR')
return ip
class MyReportsView(LoginRequiredMixin, ListView):
"""List user's own reports."""
model = ScamReport
template_name = 'reports/my_reports.html'
context_object_name = 'reports'
paginate_by = 20
def get_queryset(self):
return ScamReport.objects.filter(
reporter=self.request.user
).prefetch_related('moderation_actions').order_by('-created_at')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Get the first rejection action for each report
for report in context['reports']:
rejection_action = report.moderation_actions.filter(action_type='reject').first()
report.rejection_reason = rejection_action.reason if rejection_action else None
report.rejection_action = rejection_action
return context
class ReportEditView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
"""Edit own report (only if pending or rejected)."""
model = ScamReport
form_class = ScamReportForm
template_name = 'reports/edit.html'
success_url = reverse_lazy('reports:my_reports')
success_message = "Report updated successfully!"
def get_form_kwargs(self):
"""Pass request to form for rate limiting."""
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get_queryset(self):
# Allow editing pending or rejected reports
return ScamReport.objects.filter(
reporter=self.request.user,
status__in=['pending', 'rejected']
)
def form_valid(self, form):
# If editing a rejected report, change status back to pending for re-review
if form.instance.status == 'rejected':
form.instance.status = 'pending'
self.success_message = "Report updated and resubmitted for review!"
return super().form_valid(form)
class ReportDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
"""Delete own report (only if pending or rejected)."""
model = ScamReport
template_name = 'reports/delete.html'
success_url = reverse_lazy('reports:my_reports')
success_message = "Report deleted successfully!"
def get_queryset(self):
# Allow deleting pending or rejected reports
return ScamReport.objects.filter(
reporter=self.request.user,
status__in=['pending', 'rejected']
)
class ReportSearchView(ListView):
"""Search reports."""
model = ScamReport
template_name = 'reports/search.html'
context_object_name = 'reports'
paginate_by = 20
def get_queryset(self):
query = self.request.GET.get('q', '')
scam_type = self.request.GET.get('type', '')
queryset = ScamReport.objects.filter(
is_public=True,
status='verified'
)
if query:
queryset = queryset.filter(
Q(title__icontains=query) |
Q(description__icontains=query) |
Q(reported_url__icontains=query) |
Q(reported_email__icontains=query)
)
if scam_type:
queryset = queryset.filter(scam_type=scam_type)
return queryset.select_related('reporter').prefetch_related('tags')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['scam_type_choices'] = ScamReport.SCAM_TYPE_CHOICES
# SEO metadata
query = self.request.GET.get('q', '')
if query:
self.request.seo_title = f'Търсене: {query} - Доклади за Измами'
self.request.seo_description = f'Резултати от търсенето за "{query}" в базата данни с доклади за измами.'
else:
self.request.seo_title = 'Търсене на Доклади - Официален Портал'
self.request.seo_description = 'Търсете в базата данни с верифицирани доклади за измами в България.'
self.request.seo_keywords = 'търсене измами, доклади, база данни измами'
self.request.canonical_url = self.request.build_absolute_uri('/search/')
return context
class ContactView(SuccessMessageMixin, FormView):
"""Contact us page."""
form_class = ContactForm
template_name = 'reports/contact.html'
success_url = reverse_lazy('reports:contact')
success_message = "Благодарим ви! Вашето съобщение е изпратено успешно. Ще се свържем с вас скоро."
def get_form_kwargs(self):
"""Pass request to form for rate limiting."""
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def form_valid(self, form):
# Send email notification (if email is configured)
try:
subject = f"[Контакт] {form.cleaned_data['subject']}"
message = f"""
Име: {form.cleaned_data['name']}
Имейл: {form.cleaned_data['email']}
Тип заявка: {form.cleaned_data['inquiry_type']}
Съобщение:
{form.cleaned_data['message']}
"""
from .models import SiteSettings
site_settings = SiteSettings.get_settings()
from_email = site_settings.default_from_email
recipient_list = [site_settings.contact_email]
send_mail(
subject,
message,
from_email,
recipient_list,
fail_silently=True, # Don't fail if email is not configured
)
except Exception:
pass # Email sending is optional
messages.success(self.request, self.success_message)
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add contact information from SiteSettings (already in context via context processor)
# But we can add it explicitly if needed for backward compatibility
from .models import SiteSettings
site_settings = SiteSettings.get_settings()
context['contact_email'] = site_settings.contact_email
context['contact_phone'] = site_settings.contact_phone
context['contact_address'] = site_settings.contact_address
# SEO metadata
self.request.seo_title = 'Контакти - Официален Портал за Докладване на Измами'
self.request.seo_description = 'Свържете се с нас за въпроси, обратна връзка или техническа поддръжка. Портал за докладване на измами в България.'
self.request.seo_keywords = 'контакти, поддръжка, обратна връзка, свържете се, официален портал'
self.request.canonical_url = self.request.build_absolute_uri('/contact/')
return context
class TakedownRequestView(SuccessMessageMixin, FormView):
"""View for requesting takedown of a scam report."""
form_class = TakedownRequestForm
template_name = 'reports/takedown_request.html'
success_message = "Вашата заявка за премахване е изпратена успешно. Ще бъде прегледана от нашия екип в рамките на 2-5 работни дни."
def dispatch(self, request, *args, **kwargs):
# Get the report
self.report = get_object_or_404(
ScamReport.objects.filter(is_public=True, status='verified'),
pk=kwargs['report_pk']
)
return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
"""Pass request to form for rate limiting."""
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['report'] = self.report
# SEO metadata
self.request.seo_title = f'Заявка за Премахване - {self.report.title}'
self.request.seo_description = 'Заявка за премахване на доклад за измама'
self.request.canonical_url = self.request.build_absolute_uri()
self.request.meta_robots = 'noindex, nofollow'
return context
def form_valid(self, form):
# Get client IP
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip_address = x_forwarded_for.split(',')[0]
else:
ip_address = self.request.META.get('REMOTE_ADDR')
# Create takedown request
takedown_request = form.save(commit=False)
takedown_request.report = self.report
takedown_request.ip_address = ip_address
takedown_request.user_agent = self.request.META.get('HTTP_USER_AGENT', '')
takedown_request.save()
# Send email notification (if email is configured)
try:
from .models import SiteSettings
site_settings = SiteSettings.get_settings()
subject = f"[Заявка за Премахване] Доклад: {self.report.title}"
message = f"""
Нова заявка за премахване на доклад:
Доклад: {self.report.title} (ID: {self.report.pk})
URL: {self.request.build_absolute_uri(self.report.get_absolute_url())}
Заявител:
Име: {form.cleaned_data['requester_name']}
Имейл: {form.cleaned_data['requester_email']}
Телефон: {form.cleaned_data.get('requester_phone', 'Не е предоставен')}
Причина:
{form.cleaned_data['reason']}
Доказателства:
{form.cleaned_data.get('evidence', 'Не са предоставени')}
---
IP адрес: {ip_address}
Дата: {takedown_request.created_at}
"""
from_email = site_settings.default_from_email
recipient_list = [site_settings.contact_email]
send_mail(
subject,
message,
from_email,
recipient_list,
fail_silently=True,
)
except Exception:
pass # Email sending is optional
messages.success(self.request, self.success_message)
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('reports:detail', kwargs={'pk': self.report.pk})