Виджет с усреднёнными точками на карте
This commit is contained in:
@@ -6,13 +6,14 @@ 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.db.models import Count, Q
|
||||
from django.http import JsonResponse
|
||||
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 ..models import Source, Satellite
|
||||
from ..utils import parse_pagination_params
|
||||
|
||||
|
||||
@@ -38,6 +39,15 @@ class SourceListView(LoginRequiredMixin, View):
|
||||
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()
|
||||
selected_satellites = request.GET.getlist("satellite_id")
|
||||
|
||||
# Get all satellites for filter
|
||||
satellites = (
|
||||
Satellite.objects.filter(parameters__objitem__source__isnull=False)
|
||||
.distinct()
|
||||
.only("id", "name")
|
||||
.order_by("name")
|
||||
)
|
||||
|
||||
# Get all Source objects with query optimization
|
||||
# Using annotate to count ObjItems efficiently (single query with GROUP BY)
|
||||
@@ -45,6 +55,7 @@ class SourceListView(LoginRequiredMixin, View):
|
||||
sources = Source.objects.prefetch_related(
|
||||
'source_objitems',
|
||||
'source_objitems__parameter_obj',
|
||||
'source_objitems__parameter_obj__id_satellite',
|
||||
'source_objitems__geo_obj'
|
||||
).annotate(
|
||||
objitem_count=Count('source_objitems')
|
||||
@@ -117,6 +128,12 @@ class SourceListView(LoginRequiredMixin, View):
|
||||
# If not a number, ignore
|
||||
pass
|
||||
|
||||
# Filter by satellites
|
||||
if selected_satellites:
|
||||
sources = sources.filter(
|
||||
source_objitems__parameter_obj__id_satellite_id__in=selected_satellites
|
||||
).distinct()
|
||||
|
||||
# Apply sorting
|
||||
valid_sort_fields = {
|
||||
"id": "id",
|
||||
@@ -157,6 +174,15 @@ class SourceListView(LoginRequiredMixin, View):
|
||||
# Get count of related ObjItems
|
||||
objitem_count = source.objitem_count
|
||||
|
||||
# Get satellites for this source
|
||||
satellite_names = set()
|
||||
for objitem in source.source_objitems.all():
|
||||
if hasattr(objitem, 'parameter_obj') and objitem.parameter_obj:
|
||||
if hasattr(objitem.parameter_obj, 'id_satellite') and objitem.parameter_obj.id_satellite:
|
||||
satellite_names.add(objitem.parameter_obj.id_satellite.name)
|
||||
|
||||
satellite_str = ", ".join(sorted(satellite_names)) if satellite_names else "-"
|
||||
|
||||
processed_sources.append({
|
||||
'id': source.id,
|
||||
'coords_average': coords_average_str,
|
||||
@@ -164,6 +190,7 @@ class SourceListView(LoginRequiredMixin, View):
|
||||
'coords_valid': coords_valid_str,
|
||||
'coords_reference': coords_reference_str,
|
||||
'objitem_count': objitem_count,
|
||||
'satellite': satellite_str,
|
||||
'created_at': source.created_at,
|
||||
'updated_at': source.updated_at,
|
||||
})
|
||||
@@ -184,6 +211,10 @@ class SourceListView(LoginRequiredMixin, View):
|
||||
'objitem_count_max': objitem_count_max,
|
||||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
'satellites': satellites,
|
||||
'selected_satellites': [
|
||||
int(x) if isinstance(x, str) else x for x in selected_satellites if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
|
||||
],
|
||||
'full_width_page': True,
|
||||
}
|
||||
|
||||
@@ -302,3 +333,89 @@ class SourceDeleteView(LoginRequiredMixin, AdminModeratorMixin, View):
|
||||
if request.GET.urlencode():
|
||||
return redirect(f"{reverse('mainapp:home')}?{request.GET.urlencode()}")
|
||||
return redirect('mainapp:home')
|
||||
|
||||
|
||||
class DeleteSelectedSourcesView(LoginRequiredMixin, AdminModeratorMixin, View):
|
||||
"""View for deleting multiple selected sources with confirmation."""
|
||||
|
||||
def get(self, request):
|
||||
"""Show confirmation page with details about sources to be deleted."""
|
||||
ids = request.GET.get("ids", "")
|
||||
if not ids:
|
||||
messages.error(request, "Не выбраны источники для удаления")
|
||||
return redirect('mainapp:home')
|
||||
|
||||
try:
|
||||
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
|
||||
sources = Source.objects.filter(id__in=id_list).prefetch_related(
|
||||
'source_objitems',
|
||||
'source_objitems__parameter_obj',
|
||||
'source_objitems__parameter_obj__id_satellite',
|
||||
'source_objitems__geo_obj'
|
||||
).annotate(
|
||||
objitem_count=Count('source_objitems')
|
||||
)
|
||||
|
||||
# Prepare detailed information about sources
|
||||
sources_info = []
|
||||
total_objitems = 0
|
||||
|
||||
for source in sources:
|
||||
# Get satellites for this source
|
||||
satellite_names = set()
|
||||
for objitem in source.source_objitems.all():
|
||||
if hasattr(objitem, 'parameter_obj') and objitem.parameter_obj:
|
||||
if hasattr(objitem.parameter_obj, 'id_satellite') and objitem.parameter_obj.id_satellite:
|
||||
satellite_names.add(objitem.parameter_obj.id_satellite.name)
|
||||
|
||||
objitem_count = source.objitem_count
|
||||
total_objitems += objitem_count
|
||||
|
||||
sources_info.append({
|
||||
'id': source.id,
|
||||
'objitem_count': objitem_count,
|
||||
'satellites': ", ".join(sorted(satellite_names)) if satellite_names else "-",
|
||||
})
|
||||
|
||||
context = {
|
||||
'sources_info': sources_info,
|
||||
'total_sources': len(sources_info),
|
||||
'total_objitems': total_objitems,
|
||||
'ids': ids,
|
||||
}
|
||||
|
||||
return render(request, 'mainapp/source_bulk_delete_confirm.html', context)
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f'Ошибка при подготовке удаления: {str(e)}')
|
||||
return redirect('mainapp:home')
|
||||
|
||||
def post(self, request):
|
||||
"""Actually delete the selected sources."""
|
||||
ids = request.POST.get("ids", "")
|
||||
if not ids:
|
||||
return JsonResponse({"error": "Нет ID для удаления"}, status=400)
|
||||
|
||||
try:
|
||||
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
|
||||
|
||||
# Get count before deletion
|
||||
sources = Source.objects.filter(id__in=id_list)
|
||||
deleted_sources_count = sources.count()
|
||||
|
||||
# Delete sources (cascade will delete related objitems)
|
||||
sources.delete()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
f'Успешно удалено источников: {deleted_sources_count}'
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
"success": True,
|
||||
"message": f"Успешно удалено источников: {deleted_sources_count}",
|
||||
"deleted_count": deleted_sources_count,
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({"error": f"Ошибка при удалении: {str(e)}"}, status=500)
|
||||
|
||||
Reference in New Issue
Block a user