305 lines
11 KiB
Python
305 lines
11 KiB
Python
"""
|
||
Source related views.
|
||
"""
|
||
from datetime import datetime
|
||
|
||
from django.contrib import messages
|
||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||
from django.core.paginator import Paginator
|
||
from django.db.models import Count
|
||
from django.shortcuts import get_object_or_404, redirect, render
|
||
from django.urls import reverse
|
||
from django.views import View
|
||
|
||
from ..forms import SourceForm
|
||
from ..models import Source
|
||
from ..utils import parse_pagination_params
|
||
|
||
|
||
class SourceListView(LoginRequiredMixin, View):
|
||
"""
|
||
View for displaying a list of sources (Source).
|
||
"""
|
||
|
||
def get(self, request):
|
||
# Get pagination parameters
|
||
page_number, items_per_page = parse_pagination_params(request)
|
||
|
||
# Get sorting parameters (default to ID ascending)
|
||
sort_param = request.GET.get("sort", "id")
|
||
|
||
# Get filter parameters
|
||
search_query = request.GET.get("search", "").strip()
|
||
has_coords_average = request.GET.get("has_coords_average")
|
||
has_coords_kupsat = request.GET.get("has_coords_kupsat")
|
||
has_coords_valid = request.GET.get("has_coords_valid")
|
||
has_coords_reference = request.GET.get("has_coords_reference")
|
||
objitem_count_min = request.GET.get("objitem_count_min", "").strip()
|
||
objitem_count_max = request.GET.get("objitem_count_max", "").strip()
|
||
date_from = request.GET.get("date_from", "").strip()
|
||
date_to = request.GET.get("date_to", "").strip()
|
||
|
||
# Get all Source objects with query optimization
|
||
# Using annotate to count ObjItems efficiently (single query with GROUP BY)
|
||
# Using prefetch_related for reverse ForeignKey relationships to avoid N+1 queries
|
||
sources = Source.objects.prefetch_related(
|
||
'source_objitems',
|
||
'source_objitems__parameter_obj',
|
||
'source_objitems__geo_obj'
|
||
).annotate(
|
||
objitem_count=Count('source_objitems')
|
||
)
|
||
|
||
# Apply filters
|
||
# Filter by coords_average presence
|
||
if has_coords_average == "1":
|
||
sources = sources.filter(coords_average__isnull=False)
|
||
elif has_coords_average == "0":
|
||
sources = sources.filter(coords_average__isnull=True)
|
||
|
||
# Filter by coords_kupsat presence
|
||
if has_coords_kupsat == "1":
|
||
sources = sources.filter(coords_kupsat__isnull=False)
|
||
elif has_coords_kupsat == "0":
|
||
sources = sources.filter(coords_kupsat__isnull=True)
|
||
|
||
# Filter by coords_valid presence
|
||
if has_coords_valid == "1":
|
||
sources = sources.filter(coords_valid__isnull=False)
|
||
elif has_coords_valid == "0":
|
||
sources = sources.filter(coords_valid__isnull=True)
|
||
|
||
# Filter by coords_reference presence
|
||
if has_coords_reference == "1":
|
||
sources = sources.filter(coords_reference__isnull=False)
|
||
elif has_coords_reference == "0":
|
||
sources = sources.filter(coords_reference__isnull=True)
|
||
|
||
# Filter by ObjItem count
|
||
if objitem_count_min:
|
||
try:
|
||
min_count = int(objitem_count_min)
|
||
sources = sources.filter(objitem_count__gte=min_count)
|
||
except ValueError:
|
||
pass
|
||
|
||
if objitem_count_max:
|
||
try:
|
||
max_count = int(objitem_count_max)
|
||
sources = sources.filter(objitem_count__lte=max_count)
|
||
except ValueError:
|
||
pass
|
||
|
||
# Filter by creation date range
|
||
if date_from:
|
||
try:
|
||
date_from_obj = datetime.strptime(date_from, "%Y-%m-%d")
|
||
sources = sources.filter(created_at__gte=date_from_obj)
|
||
except (ValueError, TypeError):
|
||
pass
|
||
|
||
if date_to:
|
||
try:
|
||
from datetime import timedelta
|
||
date_to_obj = datetime.strptime(date_to, "%Y-%m-%d")
|
||
# Add one day to include entire end date
|
||
date_to_obj = date_to_obj + timedelta(days=1)
|
||
sources = sources.filter(created_at__lt=date_to_obj)
|
||
except (ValueError, TypeError):
|
||
pass
|
||
|
||
# Search by ID
|
||
if search_query:
|
||
try:
|
||
search_id = int(search_query)
|
||
sources = sources.filter(id=search_id)
|
||
except ValueError:
|
||
# If not a number, ignore
|
||
pass
|
||
|
||
# Apply sorting
|
||
valid_sort_fields = {
|
||
"id": "id",
|
||
"-id": "-id",
|
||
"created_at": "created_at",
|
||
"-created_at": "-created_at",
|
||
"updated_at": "updated_at",
|
||
"-updated_at": "-updated_at",
|
||
"objitem_count": "objitem_count",
|
||
"-objitem_count": "-objitem_count",
|
||
}
|
||
|
||
if sort_param in valid_sort_fields:
|
||
sources = sources.order_by(valid_sort_fields[sort_param])
|
||
|
||
# Create paginator
|
||
paginator = Paginator(sources, items_per_page)
|
||
page_obj = paginator.get_page(page_number)
|
||
|
||
# Prepare data for display
|
||
processed_sources = []
|
||
for source in page_obj:
|
||
# Format coordinates
|
||
def format_coords(point):
|
||
if point:
|
||
longitude = point.coords[0]
|
||
latitude = point.coords[1]
|
||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
||
return f"{lat} {lon}"
|
||
return "-"
|
||
|
||
coords_average_str = format_coords(source.coords_average)
|
||
coords_kupsat_str = format_coords(source.coords_kupsat)
|
||
coords_valid_str = format_coords(source.coords_valid)
|
||
coords_reference_str = format_coords(source.coords_reference)
|
||
|
||
# Get count of related ObjItems
|
||
objitem_count = source.objitem_count
|
||
|
||
processed_sources.append({
|
||
'id': source.id,
|
||
'coords_average': coords_average_str,
|
||
'coords_kupsat': coords_kupsat_str,
|
||
'coords_valid': coords_valid_str,
|
||
'coords_reference': coords_reference_str,
|
||
'objitem_count': objitem_count,
|
||
'created_at': source.created_at,
|
||
'updated_at': source.updated_at,
|
||
})
|
||
|
||
# Prepare context for template
|
||
context = {
|
||
'page_obj': page_obj,
|
||
'processed_sources': processed_sources,
|
||
'items_per_page': items_per_page,
|
||
'available_items_per_page': [50, 100, 500, 1000],
|
||
'sort': sort_param,
|
||
'search_query': search_query,
|
||
'has_coords_average': has_coords_average,
|
||
'has_coords_kupsat': has_coords_kupsat,
|
||
'has_coords_valid': has_coords_valid,
|
||
'has_coords_reference': has_coords_reference,
|
||
'objitem_count_min': objitem_count_min,
|
||
'objitem_count_max': objitem_count_max,
|
||
'date_from': date_from,
|
||
'date_to': date_to,
|
||
'full_width_page': True,
|
||
}
|
||
|
||
return render(request, "mainapp/source_list.html", context)
|
||
|
||
|
||
|
||
class AdminModeratorMixin(UserPassesTestMixin):
|
||
"""Mixin to restrict access to admin and moderator roles only."""
|
||
|
||
def test_func(self):
|
||
return (
|
||
self.request.user.is_authenticated and
|
||
hasattr(self.request.user, 'customuser') and
|
||
self.request.user.customuser.role in ['admin', 'moderator']
|
||
)
|
||
|
||
def handle_no_permission(self):
|
||
messages.error(self.request, 'У вас нет прав для выполнения этого действия.')
|
||
return redirect('mainapp:home')
|
||
|
||
|
||
class SourceUpdateView(LoginRequiredMixin, AdminModeratorMixin, View):
|
||
"""View for editing Source with 4 coordinate fields and related ObjItems."""
|
||
|
||
def get(self, request, pk):
|
||
source = get_object_or_404(Source, pk=pk)
|
||
form = SourceForm(instance=source)
|
||
|
||
# Get related ObjItems ordered by creation date
|
||
objitems = source.source_objitems.select_related(
|
||
'parameter_obj',
|
||
'parameter_obj__id_satellite',
|
||
'parameter_obj__polarization',
|
||
'parameter_obj__modulation',
|
||
'parameter_obj__standard',
|
||
'geo_obj',
|
||
'created_by__user',
|
||
'updated_by__user'
|
||
).order_by('created_at')
|
||
|
||
context = {
|
||
'object': source,
|
||
'form': form,
|
||
'objitems': objitems,
|
||
'full_width_page': True,
|
||
}
|
||
|
||
return render(request, 'mainapp/source_form.html', context)
|
||
|
||
def post(self, request, pk):
|
||
source = get_object_or_404(Source, pk=pk)
|
||
form = SourceForm(request.POST, instance=source)
|
||
|
||
if form.is_valid():
|
||
source = form.save(commit=False)
|
||
# Set updated_by to current user
|
||
if hasattr(request.user, 'customuser'):
|
||
source.updated_by = request.user.customuser
|
||
source.save()
|
||
|
||
messages.success(request, f'Источник #{source.id} успешно обновлен.')
|
||
|
||
# Redirect back with query params if present
|
||
if request.GET.urlencode():
|
||
return redirect(f"{reverse('mainapp:source_update', args=[source.id])}?{request.GET.urlencode()}")
|
||
return redirect('mainapp:source_update', pk=source.id)
|
||
|
||
# If form is invalid, re-render with errors
|
||
objitems = source.source_objitems.select_related(
|
||
'parameter_obj',
|
||
'parameter_obj__id_satellite',
|
||
'parameter_obj__polarization',
|
||
'parameter_obj__modulation',
|
||
'parameter_obj__standard',
|
||
'geo_obj',
|
||
'created_by__user',
|
||
'updated_by__user'
|
||
).order_by('created_at')
|
||
|
||
context = {
|
||
'object': source,
|
||
'form': form,
|
||
'objitems': objitems,
|
||
'full_width_page': True,
|
||
}
|
||
|
||
return render(request, 'mainapp/source_form.html', context)
|
||
|
||
|
||
class SourceDeleteView(LoginRequiredMixin, AdminModeratorMixin, View):
|
||
"""View for deleting Source."""
|
||
|
||
def get(self, request, pk):
|
||
source = get_object_or_404(Source, pk=pk)
|
||
|
||
context = {
|
||
'object': source,
|
||
'objitems_count': source.source_objitems.count(),
|
||
}
|
||
|
||
return render(request, 'mainapp/source_confirm_delete.html', context)
|
||
|
||
def post(self, request, pk):
|
||
source = get_object_or_404(Source, pk=pk)
|
||
source_id = source.id
|
||
|
||
try:
|
||
source.delete()
|
||
messages.success(request, f'Источник #{source_id} успешно удален.')
|
||
except Exception as e:
|
||
messages.error(request, f'Ошибка при удалении источника: {str(e)}')
|
||
return redirect('mainapp:source_update', pk=pk)
|
||
|
||
# Redirect to source list
|
||
if request.GET.urlencode():
|
||
return redirect(f"{reverse('mainapp:home')}?{request.GET.urlencode()}")
|
||
return redirect('mainapp:home')
|