update
This commit is contained in:
454
reports/views.py
Normal file
454
reports/views.py
Normal file
@@ -0,0 +1,454 @@
|
||||
"""
|
||||
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})
|
||||
Reference in New Issue
Block a user