""" 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})