Сделал вкладку спутников

This commit is contained in:
2025-11-20 13:44:48 +03:00
parent 1d1c42a8e7
commit c2c8c8799f
10 changed files with 1582 additions and 6 deletions

View File

@@ -0,0 +1,353 @@
"""
Satellite CRUD operations and related views.
"""
from datetime import datetime
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
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, reverse_lazy
from django.views import View
from django.views.generic import CreateView, UpdateView
from ..forms import SatelliteForm
from ..mixins import RoleRequiredMixin, FormMessageMixin
from ..models import Satellite, Band
from ..utils import parse_pagination_params
class SatelliteListView(LoginRequiredMixin, View):
"""View for displaying a list of satellites with filtering and pagination."""
def get(self, request):
# Get pagination parameters
page_number, items_per_page = parse_pagination_params(request)
# Get sorting parameters (default to name)
sort_param = request.GET.get("sort", "name")
# Get filter parameters
search_query = request.GET.get("search", "").strip()
selected_bands = request.GET.getlist("band_id")
norad_min = request.GET.get("norad_min", "").strip()
norad_max = request.GET.get("norad_max", "").strip()
undersat_point_min = request.GET.get("undersat_point_min", "").strip()
undersat_point_max = request.GET.get("undersat_point_max", "").strip()
launch_date_from = request.GET.get("launch_date_from", "").strip()
launch_date_to = request.GET.get("launch_date_to", "").strip()
date_from = request.GET.get("date_from", "").strip()
date_to = request.GET.get("date_to", "").strip()
# Get all bands for filters
bands = Band.objects.all().order_by("name")
# Get all satellites with query optimization
satellites = Satellite.objects.prefetch_related(
'band',
'created_by__user',
'updated_by__user'
).annotate(
transponder_count=Count('tran_satellite', distinct=True)
)
# Apply filters
# Filter by bands
if selected_bands:
satellites = satellites.filter(band__id__in=selected_bands).distinct()
# Filter by NORAD ID
if norad_min:
try:
min_val = int(norad_min)
satellites = satellites.filter(norad__gte=min_val)
except ValueError:
pass
if norad_max:
try:
max_val = int(norad_max)
satellites = satellites.filter(norad__lte=max_val)
except ValueError:
pass
# Filter by undersat point
if undersat_point_min:
try:
min_val = float(undersat_point_min)
satellites = satellites.filter(undersat_point__gte=min_val)
except ValueError:
pass
if undersat_point_max:
try:
max_val = float(undersat_point_max)
satellites = satellites.filter(undersat_point__lte=max_val)
except ValueError:
pass
# Filter by launch date range
if launch_date_from:
try:
date_from_obj = datetime.strptime(launch_date_from, "%Y-%m-%d").date()
satellites = satellites.filter(launch_date__gte=date_from_obj)
except (ValueError, TypeError):
pass
if launch_date_to:
try:
from datetime import timedelta
date_to_obj = datetime.strptime(launch_date_to, "%Y-%m-%d").date()
# Add one day to include entire end date
date_to_obj = date_to_obj + timedelta(days=1)
satellites = satellites.filter(launch_date__lt=date_to_obj)
except (ValueError, TypeError):
pass
# Filter by creation date range
if date_from:
try:
date_from_obj = datetime.strptime(date_from, "%Y-%m-%d")
satellites = satellites.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)
satellites = satellites.filter(created_at__lt=date_to_obj)
except (ValueError, TypeError):
pass
# Search by name
if search_query:
satellites = satellites.filter(
Q(name__icontains=search_query) |
Q(comment__icontains=search_query)
)
# Apply sorting
valid_sort_fields = {
"id": "id",
"-id": "-id",
"name": "name",
"-name": "-name",
"norad": "norad",
"-norad": "-norad",
"undersat_point": "undersat_point",
"-undersat_point": "-undersat_point",
"launch_date": "launch_date",
"-launch_date": "-launch_date",
"created_at": "created_at",
"-created_at": "-created_at",
"updated_at": "updated_at",
"-updated_at": "-updated_at",
"transponder_count": "transponder_count",
"-transponder_count": "-transponder_count",
}
if sort_param in valid_sort_fields:
satellites = satellites.order_by(valid_sort_fields[sort_param])
# Create paginator
paginator = Paginator(satellites, items_per_page)
page_obj = paginator.get_page(page_number)
# Prepare data for display
processed_satellites = []
for satellite in page_obj:
# Get band names
band_names = [band.name for band in satellite.band.all()]
processed_satellites.append({
'id': satellite.id,
'name': satellite.name or "-",
'norad': satellite.norad if satellite.norad else "-",
'bands': ", ".join(band_names) if band_names else "-",
'undersat_point': f"{satellite.undersat_point:.2f}" if satellite.undersat_point is not None else "-",
'launch_date': satellite.launch_date.strftime("%d.%m.%Y") if satellite.launch_date else "-",
'url': satellite.url or "-",
'comment': satellite.comment or "-",
'transponder_count': satellite.transponder_count,
'created_at': satellite.created_at,
'updated_at': satellite.updated_at,
'created_by': satellite.created_by if satellite.created_by else "-",
'updated_by': satellite.updated_by if satellite.updated_by else "-",
})
# Prepare context for template
context = {
'page_obj': page_obj,
'processed_satellites': processed_satellites,
'items_per_page': items_per_page,
'available_items_per_page': [50, 100, 500, 1000],
'sort': sort_param,
'search_query': search_query,
'bands': bands,
'selected_bands': [
int(x) if isinstance(x, str) else x for x in selected_bands
if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
],
'norad_min': norad_min,
'norad_max': norad_max,
'undersat_point_min': undersat_point_min,
'undersat_point_max': undersat_point_max,
'launch_date_from': launch_date_from,
'launch_date_to': launch_date_to,
'date_from': date_from,
'date_to': date_to,
'full_width_page': True,
}
return render(request, "mainapp/satellite_list.html", context)
class SatelliteCreateView(RoleRequiredMixin, FormMessageMixin, CreateView):
"""View for creating a new satellite."""
model = Satellite
form_class = SatelliteForm
template_name = "mainapp/satellite_form.html"
success_url = reverse_lazy("mainapp:satellite_list")
success_message = "Спутник успешно создан!"
required_roles = ["admin", "moderator"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['action'] = 'create'
context['title'] = 'Создание спутника'
return context
def form_valid(self, form):
form.instance.created_by = self.request.user.customuser
form.instance.updated_by = self.request.user.customuser
return super().form_valid(form)
class SatelliteUpdateView(RoleRequiredMixin, FormMessageMixin, UpdateView):
"""View for updating an existing satellite."""
model = Satellite
form_class = SatelliteForm
template_name = "mainapp/satellite_form.html"
success_url = reverse_lazy("mainapp:satellite_list")
success_message = "Спутник успешно обновлен!"
required_roles = ["admin", "moderator"]
def get_context_data(self, **kwargs):
import json
context = super().get_context_data(**kwargs)
context['action'] = 'update'
context['title'] = f'Редактирование спутника: {self.object.name}'
# Get transponders for this satellite for frequency plan
from mapsapp.models import Transponders
transponders = Transponders.objects.filter(
sat_id=self.object
).select_related('polarization').order_by('downlink')
# Prepare transponder data for frequency plan visualization
transponder_data = []
for t in transponders:
if t.downlink and t.frequency_range:
transponder_data.append({
'id': t.id,
'name': t.name or f"TP-{t.id}",
'downlink': float(t.downlink),
'frequency_range': float(t.frequency_range),
'polarization': t.polarization.name if t.polarization else '-',
'zone_name': t.zone_name or '-',
})
context['transponders'] = json.dumps(transponder_data)
context['transponder_count'] = len(transponder_data)
return context
def form_valid(self, form):
form.instance.updated_by = self.request.user.customuser
return super().form_valid(form)
class DeleteSelectedSatellitesView(RoleRequiredMixin, View):
"""View for deleting multiple selected satellites with confirmation."""
required_roles = ["admin", "moderator"]
def get(self, request):
"""Show confirmation page with details about satellites to be deleted."""
ids = request.GET.get("ids", "")
if not ids:
messages.error(request, "Не выбраны спутники для удаления")
return redirect('mainapp:satellite_list')
try:
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
satellites = Satellite.objects.filter(id__in=id_list).prefetch_related(
'band'
).annotate(
transponder_count=Count('tran_satellite', distinct=True)
)
# Prepare detailed information about satellites
satellites_info = []
total_transponders = 0
for satellite in satellites:
transponder_count = satellite.transponder_count
total_transponders += transponder_count
satellites_info.append({
'id': satellite.id,
'name': satellite.name or "-",
'norad': satellite.norad if satellite.norad else "-",
'transponder_count': transponder_count,
})
context = {
'satellites_info': satellites_info,
'total_satellites': len(satellites_info),
'total_transponders': total_transponders,
'ids': ids,
}
return render(request, 'mainapp/satellite_bulk_delete_confirm.html', context)
except Exception as e:
messages.error(request, f'Ошибка при подготовке удаления: {str(e)}')
return redirect('mainapp:satellite_list')
def post(self, request):
"""Actually delete the selected satellites."""
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
satellites = Satellite.objects.filter(id__in=id_list)
deleted_count = satellites.count()
# Delete satellites (cascade will handle related objects)
satellites.delete()
messages.success(
request,
f'Успешно удалено спутников: {deleted_count}'
)
return JsonResponse({
"success": True,
"message": f"Успешно удалено спутников: {deleted_count}",
"deleted_count": deleted_count,
})
except Exception as e:
return JsonResponse({"error": f"Ошибка при удалении: {str(e)}"}, status=500)