# Standard library imports from typing import Any, Dict # Django imports from django.contrib.auth.mixins import LoginRequiredMixin from django.http import HttpResponse, HttpResponseNotFound, JsonResponse from django.utils.decorators import method_decorator from django.views import View from django.views.decorators.cache import cache_page from django.views.decorators.http import require_GET from django.views.generic import TemplateView # Third-party imports import requests # Local imports from mainapp.models import Satellite from .models import Transponders from .utils import get_band_names class CesiumMapView(LoginRequiredMixin, TemplateView): """ Представление для отображения 3D карты с использованием Cesium. Отображает спутники и их зоны покрытия на интерактивной 3D карте. """ template_name = "mapsapp/map3d.html" def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: context = super().get_context_data(**kwargs) # Оптимизированный запрос - загружаем только необходимые поля # Фильтруем спутники, у которых есть параметры с привязанными объектами context["sats"] = ( Satellite.objects.filter(parameters__objitem__isnull=False) .distinct() .only("id", "name") .order_by("name") ) return context class GetFootprintsView(LoginRequiredMixin, View): """ API для получения зон покрытия (footprints) спутника. Возвращает список названий зон покрытия для указанного спутника. """ def get(self, request, sat_id): try: # Оптимизированный запрос - загружаем только поле name sat_name = Satellite.objects.only("name").get(id=sat_id).name footprint_names = get_band_names(sat_name) return JsonResponse(footprint_names, safe=False) except Satellite.DoesNotExist: return JsonResponse({"error": "Спутник не найден"}, status=404) except Exception as e: return JsonResponse({"error": str(e)}, status=500) class TileProxyView(View): """ Прокси для загрузки тайлов карты покрытия спутников. Кэширует тайлы на 7 дней для улучшения производительности. """ # Константы TILE_BASE_URL = "https://static.satbeams.com/tiles" CACHE_DURATION = 60 * 60 * 24 * 7 # 7 дней REQUEST_TIMEOUT = 10 # секунд @method_decorator(require_GET) @method_decorator(cache_page(CACHE_DURATION)) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) def get(self, request, footprint_name, z, x, y): # Валидация имени footprint if not footprint_name.replace("-", "").replace("_", "").isalnum(): return HttpResponse("Invalid footprint name", status=400) url = f"{self.TILE_BASE_URL}/{footprint_name}/{z}/{x}/{y}.png" try: resp = requests.get(url, timeout=self.REQUEST_TIMEOUT, verify=r'/home/vesemir/DataStorage/cert.pem') if resp.status_code == 200: response = HttpResponse(resp.content, content_type="image/png") response["Access-Control-Allow-Origin"] = "*" response["Cache-Control"] = f"public, max-age={self.CACHE_DURATION}" return response else: return HttpResponseNotFound("Tile not found") except requests.Timeout: return HttpResponse("Request timeout", status=504) except requests.RequestException as e: return HttpResponse(f"Proxy error: {e}", status=500) class LeafletMapView(LoginRequiredMixin, TemplateView): """ Представление для отображения 2D карты с использованием Leaflet. Отображает спутники и транспондеры на интерактивной 2D карте. """ template_name = "mapsapp/map2d.html" def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: context = super().get_context_data(**kwargs) # Оптимизированные запросы - загружаем только необходимые поля # Фильтруем спутники, у которых есть параметры с привязанными объектами context["sats"] = ( Satellite.objects.filter(parameters__objitem__isnull=False) .distinct() .only("id", "name") .order_by("name") ) context["trans"] = Transponders.objects.select_related( "sat_id", "polarization" ).only( "id", "name", "sat_id__name", "polarization__name", "downlink", "frequency_range", "zone_name", ) return context class GetTransponderOnSatIdView(LoginRequiredMixin, View): """ API для получения транспондеров спутника. Возвращает список транспондеров для указанного спутника с оптимизированными запросами. """ def get(self, request, sat_id): # Оптимизированный запрос с select_related и only trans = ( Transponders.objects.filter(sat_id=sat_id) .select_related("polarization") .only( "name", "downlink", "frequency_range", "zone_name", "polarization__name" ) ) if not trans.exists(): return JsonResponse({"error": "Объектов не найдено"}, status=404) # Используем list comprehension для лучшей производительности output = [ { "name": tran.name, "frequency": tran.downlink, "frequency_range": tran.frequency_range, "zone_name": tran.zone_name, "polarization": tran.polarization.name if tran.polarization else "-", } for tran in trans ] return JsonResponse(output, safe=False)