This commit is contained in:
2025-10-24 16:54:20 +03:00
parent 5e40201460
commit 178854c6ba
15 changed files with 505 additions and 232 deletions

2
.gitignore vendored
View File

@@ -10,6 +10,8 @@ wheels/
.venv .venv
.hintrc .hintrc
.vscode .vscode
data.json
.env
django-leaflet django-leaflet
admin-interface admin-interface

29
dbapp/.dockerignore Normal file
View File

@@ -0,0 +1,29 @@
__pycache__
*.pyc
*.pyo
*.pyd
.Python
.pytest_cache
.coverage
.git
.gitignore
README.md
.env
.DS_Store
.settings
.vscode
.idea
*.swp
*.swo
*~
__pycache__/
*.so
.Python
.coverage
.pytest_cache
.venv
venv/
env/
.pyre/
node_modules/
.DS_Store

10
dbapp/.env.example Normal file
View File

@@ -0,0 +1,10 @@
# Production environment variables
DEBUG=False
ENVIRONMENT=production
SECRET_KEY=your_very_long_secret_key_here_change_this_to_something_secure
DB_NAME=geodb
DB_USER=geralt
DB_PASSWORD=your_secure_db_password
DB_HOST=db
DB_PORT=5432
ALLOWED_HOSTS=localhost,yourdomain.com

51
dbapp/Dockerfile Normal file
View File

@@ -0,0 +1,51 @@
# Use Python 3.13 slim image as base
FROM python:3.13.9-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app \
DJANGO_SETTINGS_MODULE=dbapp.settings.production
# Install system dependencies including GDAL and PostGIS dependencies
RUN apt-get update && apt-get install -y \
gdal-bin \
libgdal-dev \
proj-bin \
proj-data \
libproj-dev \
libgeos-dev \
postgresql-client \
build-essential \
libpq-dev \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies for GDAL
RUN pip install --upgrade pip && \
pip install --no-cache-dir GDAL==$(gdal-config --version)
# Set work directory
WORKDIR /app
# Copy project requirements
COPY pyproject.toml uv.lock ./
# Install uv package manager
RUN pip install --upgrade pip && pip install uv
# Install dependencies using uv
RUN uv pip install --system --no-cache-dir -r uv.lock
# Copy project
COPY . .
# Collect static files
RUN python manage.py collectstatic --noinput
# Expose port
EXPOSE 8000
# Run gunicorn server
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "dbapp.wsgi:application"]

73
dbapp/Dockerfile.prod Normal file
View File

@@ -0,0 +1,73 @@
# Multi-stage build for production
FROM python:3.13-slim as requirements-stage
# Install system dependencies
RUN apt-get update && apt-get install -y \
gdal-bin \
libgdal-dev \
proj-bin \
proj-data \
libproj-dev \
libgeos-dev \
build-essential \
libpq-dev \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies for GDAL
RUN pip install --upgrade pip && \
pip install --no-cache-dir GDAL==$(gdal-config --version)
WORKDIR /app
# Copy project requirements
COPY pyproject.toml uv.lock ./
# Install uv package manager
RUN pip install --upgrade pip && pip install uv
# Install dependencies using uv
RUN uv pip install --system --only-binary=gdal,shapely,pyproj --no-cache-dir -r uv.lock
# Production stage
FROM python:3.13-slim
# Install runtime system dependencies
RUN apt-get update && apt-get install -y \
gdal-bin \
libgdal30 \
libproj25 \
libgeos-c1v5 \
postgresql-client \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
DJANGO_SETTINGS_MODULE=dbapp.settings.production
# Set work directory
WORKDIR /app
# Copy Python dependencies from previous stage
COPY --from=requirements-stage /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
COPY --from=requirements-stage /usr/local/bin /usr/local/bin
# Copy project
COPY . .
# Create non-root user for security
RUN useradd --create-home --shell /bin/bash app && chown -R app:app /app
USER app
# Collect static files
RUN python manage.py collectstatic --noinput
# Expose port
EXPOSE 8000
# Run gunicorn server
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "--timeout", "120", "dbapp.wsgi:application"]

View File

@@ -0,0 +1,13 @@
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Determine the environment and import the appropriate settings
ENVIRONMENT = os.getenv('ENVIRONMENT', 'development')
if ENVIRONMENT == 'production':
from .production import *
else:
from .development import *

View File

@@ -12,6 +12,10 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
from pathlib import Path from pathlib import Path
import os import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
if os.name == 'nt': if os.name == 'nt':
OSGEO4W = r"C:\Program Files\OSGeo4W" OSGEO4W = r"C:\Program Files\OSGeo4W"
@@ -29,18 +33,17 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-7etj5f7buo2a57xv=w3^&llusq8rii7b_gd)9$t_1xcnao!^tq' SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-7etj5f7buo2a57xv=w3^&llusq8rii7b_gd)9$t_1xcnao!^tq')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = os.getenv('DEBUG', 'True').lower() == 'true'
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
# 'django_daisy',
'dal', 'dal',
'dal_select2', 'dal_select2',
"admin_interface", "admin_interface",
@@ -64,6 +67,9 @@ INSTALLED_APPS = [
'debug_toolbar' 'debug_toolbar'
] ]
# Note: Custom user model is implemented via OneToOneField relationship
# AUTH_USER_MODEL = 'mainapp.CustomUser'
MIDDLEWARE = [ MIDDLEWARE = [
"debug_toolbar.middleware.DebugToolbarMiddleware", #Добавил "debug_toolbar.middleware.DebugToolbarMiddleware", #Добавил
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
@@ -81,7 +87,9 @@ ROOT_URLCONF = 'dbapp.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [], 'DIRS': [
BASE_DIR / 'templates', # Main project templates directory
],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@@ -101,12 +109,12 @@ WSGI_APPLICATION = 'dbapp.wsgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis', 'ENGINE': os.getenv('DB_ENGINE', 'django.contrib.gis.db.backends.postgis'),
'NAME': 'geodb', 'NAME': os.getenv('DB_NAME', 'db'),
'USER': 'geralt', 'USER': os.getenv('DB_USER', 'user'),
'PASSWORD': '27082025STC', 'PASSWORD': os.getenv('DB_PASSWORD', 'password'),
'HOST': 'localhost', 'HOST': os.getenv('DB_HOST', 'localhost'),
'PORT': '5432', 'PORT': os.getenv('DB_PORT', '5432'),
} }
} }
@@ -147,7 +155,7 @@ USE_TZ = True
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'), BASE_DIR.parent / 'static', # Reference to the static directory at project root
] ]
# Default primary key field type # Default primary key field type

View File

@@ -0,0 +1,9 @@
from .base import *
# Development-specific settings
DEBUG = True
# Allow all hosts in development
ALLOWED_HOSTS = ['*']
# Additional development settings can go here

View File

@@ -0,0 +1,48 @@
from .base import *
import os
from dotenv import load_dotenv
# Production-specific settings
DEBUG = False
TEMPLATE_DEBUG = DEBUG
# In production, specify allowed hosts explicitly
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS_PROD', 'localhost,127.0.0.1').split(',')
# Security settings for production
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_SECONDS = 31536000
SECURE_REDIRECT_EXEMPT = []
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Template caching for production
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR / 'templates', # Main project templates directory
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
'loaders': [
('django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
]),
],
},
},
]
# Static files settings for production
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'

View File

@@ -5,16 +5,16 @@ from . import views
urlpatterns = [ urlpatterns = [
path('', views.home_page, name='home'), path('', views.HomePageView.as_view(), name='home'),
path('excel-data', views.load_excel_data, name='load_excel_data'), path('excel-data', views.LoadExcelDataView.as_view(), name='load_excel_data'),
path('satellites', views.add_satellites, name='add_sats'), path('satellites', views.AddSatellitesView.as_view(), name='add_sats'),
path('api/locations/<int:sat_id>/geojson/', views.get_locations, name='locations_by_id'), path('api/locations/<int:sat_id>/geojson/', views.GetLocationsView.as_view(), name='locations_by_id'),
path('transponders', views.add_transponders, name='add_trans'), path('transponders', views.AddTranspondersView.as_view(), name='add_trans'),
path('csv-data', views.load_raw_csv_data, name='load_csv_data'), path('csv-data', views.LoadCsvDataView.as_view(), name='load_csv_data'),
path('map-points/', views.show_map_view, name='admin_show_map'), path('map-points/', views.ShowMapView.as_view(), name='admin_show_map'),
path('cluster/', views.cluster_test, name='cluster'), path('cluster/', views.ClusterTestView.as_view(), name='cluster'),
path('vch-upload/', views.upload_vch_load_from_html, name='vch_load'), path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'),
path('vch-link/', views.link_vch_sigma, name='link_vch_sigma'), path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
# path('upload/', views.upload_file, name='upload_file'), # path('upload/', views.upload_file, name='upload_file'),
] ]

View File

@@ -3,6 +3,10 @@ from django.contrib import messages
from django.http import JsonResponse from django.http import JsonResponse
from django.views.decorators.http import require_GET from django.views.decorators.http import require_GET
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.utils.decorators import method_decorator
from django.views import View
from django.views.generic import TemplateView, FormView
from django.contrib.auth.mixins import UserPassesTestMixin
import pandas as pd import pandas as pd
from .utils import ( from .utils import (
fill_data_from_df, fill_data_from_df,
@@ -18,97 +22,101 @@ from .clusters import get_clusters
from dbapp.settings import BASE_DIR from dbapp.settings import BASE_DIR
def add_satellites(request): class AddSatellitesView(View):
add_satellite_list() def get(self, request):
return redirect('home') add_satellite_list()
return redirect('home')
def add_transponders(request): class AddTranspondersView(View):
try: def get(self, request):
parse_transponders_from_json(BASE_DIR / "transponders.json") try:
except FileNotFoundError: parse_transponders_from_json(BASE_DIR / "transponders.json")
print("Файл не найден") except FileNotFoundError:
return redirect('home') print("Файл не найден")
return redirect('home')
class HomePageView(TemplateView):
template_name = 'mainapp/home.html'
def home_page(request): class LoadExcelDataView(FormView):
return render(request, 'mainapp/home.html') template_name = 'mainapp/add_data_from_excel.html'
form_class = LoadExcelData
def form_valid(self, form):
uploaded_file = self.request.FILES['file']
selected_sat = form.cleaned_data['sat_choice']
number = form.cleaned_data['number_input']
try:
import io
df = pd.read_excel(io.BytesIO(uploaded_file.read()))
if number > 0:
df = df.head(number)
result = fill_data_from_df(df, selected_sat)
messages.success(self.request, f"Данные успешно загружены! Обработано строк: {result}")
return redirect('load_excel_data')
except Exception as e:
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
return redirect('load_excel_data')
def form_invalid(self, form):
messages.error(self.request, "Форма заполнена некорректно.")
return super().form_invalid(form)
def load_excel_data(request): from django.views.generic import View
if request.method == "POST":
form = LoadExcelData(request.POST, request.FILES)
if form.is_valid():
uploaded_file = request.FILES['file']
selected_sat = form.cleaned_data['sat_choice']
number = form.cleaned_data['number_input']
try: class GetLocationsView(View):
# Create a temporary file-like object from the uploaded file def get(self, request, sat_id):
import io locations = ObjItem.objects.filter(id_vch_load__id_satellite=sat_id)
df = pd.read_excel(io.BytesIO(uploaded_file.read())) if not locations:
if number > 0: return JsonResponse({'error': 'Объектов не найдено'}, status=400)
df = df.head(number)
result = fill_data_from_df(df, selected_sat)
messages.success(request, f"Данные успешно загружены! Обработано строк: {result}") features = []
return redirect('load_excel_data') for loc in locations:
except Exception as e: features.append({
messages.error(request, f"Ошибка при обработке файла: {str(e)}") "type": "Feature",
return redirect('load_excel_data') "geometry": {
else: "type": "Point",
form = LoadExcelData() "coordinates": [loc.id_geo.coords[0], loc.id_geo.coords[1]]
},
"properties": {
"pol": loc.id_vch_load.polarization.name,
"freq": loc.id_vch_load.frequency*1000000,
"name": f"{loc.name}",
"id": loc.id_geo.id
}
})
return render(request, 'mainapp/add_data_from_excel.html', {'form': form}) return JsonResponse({
"type": "FeatureCollection",
"features": features
def get_locations(request, sat_id):
locations = ObjItem.objects.filter(id_vch_load__id_satellite=sat_id)
if not locations:
return JsonResponse({'error': 'Объектов не найдено'}, status=400)
features = []
for loc in locations:
features.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [loc.id_geo.coords[0], loc.id_geo.coords[1]]
},
"properties": {
"pol": loc.id_vch_load.polarization.name,
"freq": loc.id_vch_load.frequency*1000000,
"name": f"{loc.name}",
"id": loc.id_geo.id
}
}) })
return JsonResponse({ class LoadCsvDataView(FormView):
"type": "FeatureCollection", template_name = 'mainapp/add_data_from_csv.html'
"features": features form_class = LoadCsvData
})
def load_raw_csv_data(request): def form_valid(self, form):
if request.method == "POST": uploaded_file = self.request.FILES['file']
form = LoadCsvData(request.POST, request.FILES) try:
if form.is_valid(): # Read the file content and pass it directly to the function
uploaded_file = request.FILES['file'] content = uploaded_file.read()
try: if isinstance(content, bytes):
# Read the file content and pass it directly to the function content = content.decode('utf-8')
content = uploaded_file.read()
if isinstance(content, bytes): get_points_from_csv(content)
content = content.decode('utf-8') messages.success(self.request, f"Данные успешно загружены!")
return redirect('load_csv_data')
get_points_from_csv(content) except Exception as e:
messages.success(request, f"Данные успешно загружены!") messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
return redirect('load_csv_data') return redirect('load_csv_data')
except Exception as e:
messages.error(request, f"Ошибка при обработке файла: {str(e)}")
return redirect('load_csv_data')
else:
form = LoadCsvData()
return render(request, 'mainapp/add_data_from_csv.html', {'form': form}) def form_invalid(self, form):
messages.error(self.request, "Форма заполнена некорректно.")
return super().form_invalid(form)
# def upload_file(request): # def upload_file(request):
# if request.method == 'POST' and request.FILES: # if request.method == 'POST' and request.FILES:
@@ -127,87 +135,93 @@ def load_raw_csv_data(request):
# return JsonResponse({'status': 'error', 'errors': form.errors}, status=400) # return JsonResponse({'status': 'error', 'errors': form.errors}, status=400)
# return render(request, 'mainapp/add_data_from_csv.html') # return render(request, 'mainapp/add_data_from_csv.html')
from collections import defaultdict from collections import defaultdict
@staff_member_required
def show_map_view(request):
ids = request.GET.get('ids', '')
points = []
if ids:
id_list = [int(x) for x in ids.split(',') if x.isdigit()]
locations = ObjItem.objects.filter(id__in=id_list)
for obj in locations:
points.append({
'name': f"{obj.name}",
'freq': f"{obj.id_vch_load.frequency} [{obj.id_vch_load.freq_range}] МГц",
'point': (obj.id_geo.coords.x, obj.id_geo.coords.y)
})
else:
return redirect('admin')
grouped = defaultdict(list)
for p in points:
grouped[p["name"]].append({
'point': p["point"],
'frequency': p["freq"]
})
# Преобразуем в список словарей для удобства в шаблоне @method_decorator(staff_member_required, name='dispatch')
groups = [ class ShowMapView(UserPassesTestMixin, View):
{ def test_func(self):
"name": name, return self.request.user.is_staff
"points": coords_list
}
for name, coords_list in grouped.items()
]
def get(self, request):
context = { ids = request.GET.get('ids', '')
'groups': groups, points = []
} if ids:
return render(request, 'admin/map_custom.html', context) id_list = [int(x) for x in ids.split(',') if x.isdigit()]
locations = ObjItem.objects.filter(id__in=id_list)
for obj in locations:
def cluster_test(request): points.append({
objs = ObjItem.objects.filter(name__icontains="! Astra 4A 12654,040 [1,962] МГц H") 'name': f"{obj.name}",
coords = [] 'freq': f"{obj.id_vch_load.frequency} [{obj.id_vch_load.freq_range}] МГц",
for obj in objs: 'point': (obj.id_geo.coords.x, obj.id_geo.coords.y)
coords.append((obj.id_geo.coords[1], obj.id_geo.coords[0])) })
get_clusters(coords)
return JsonResponse({"success": "ок"})
def upload_vch_load_from_html(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
selected_sat = form.cleaned_data['sat_choice']
uploaded_file = request.FILES['file']
try:
get_vch_load_from_html(uploaded_file, selected_sat)
messages.success(request, "Файл успешно обработан")
except ValueError as e:
messages.error(request, f"Ошибка при чтении таблиц: {e}")
except Exception as e:
messages.error(request, f"Неизвестная ошибка: {e}")
else: else:
messages.error(request, "Форма заполнена некорректно.") return redirect('admin')
else: grouped = defaultdict(list)
form = UploadFileForm() for p in points:
grouped[p["name"]].append({
return render(request, 'mainapp/upload_html.html', {'form': form}) 'point': p["point"],
'frequency': p["freq"]
})
# Преобразуем в список словарей для удобства в шаблоне
groups = [
{
"name": name,
"points": coords_list
}
for name, coords_list in grouped.items()
]
def link_vch_sigma(request): context = {
if request.method == 'POST': 'groups': groups,
form = VchLinkForm(request.POST) }
if form.is_valid(): return render(request, 'admin/map_custom.html', context)
freq = form.cleaned_data['value1']
freq_range = form.cleaned_data['value2']
ku_range = float(form.cleaned_data['ku_range']) class ClusterTestView(View):
sat_id = form.cleaned_data['sat_choice'] def get(self, request):
# print(freq, freq_range, ku_range, sat_id.pk) objs = ObjItem.objects.filter(name__icontains="! Astra 4A 12654,040 [1,962] МГц H")
count_all, link_count = compare_and_link_vch_load(sat_id, freq, freq_range, ku_range) coords = []
messages.success(request, f"Привязано {link_count} из {count_all} объектов") for obj in objs:
return redirect('link_vch_sigma') coords.append((obj.id_geo.coords[1], obj.id_geo.coords[0]))
else: get_clusters(coords)
form = VchLinkForm()
return JsonResponse({"success": "ок"})
return render(request, 'mainapp/link_vch.html', {'form': form})
class UploadVchLoadView(FormView):
template_name = 'mainapp/upload_html.html'
form_class = UploadFileForm
def form_valid(self, form):
selected_sat = form.cleaned_data['sat_choice']
uploaded_file = self.request.FILES['file']
try:
get_vch_load_from_html(uploaded_file, selected_sat)
messages.success(self.request, "Файл успешно обработан")
except ValueError as e:
messages.error(self.request, f"Ошибка при чтении таблиц: {e}")
except Exception as e:
messages.error(self.request, f"Неизвестная ошибка: {e}")
return redirect('vch_load')
def form_invalid(self, form):
messages.error(self.request, "Форма заполнена некорректно.")
return super().form_invalid(form)
class LinkVchSigmaView(FormView):
template_name = 'mainapp/link_vch.html'
form_class = VchLinkForm
def form_valid(self, form):
freq = form.cleaned_data['value1']
freq_range = form.cleaned_data['value2']
ku_range = float(form.cleaned_data['ku_range'])
sat_id = form.cleaned_data['sat_choice']
# print(freq, freq_range, ku_range, sat_id.pk)
count_all, link_count = compare_and_link_vch_load(sat_id, freq, freq_range, ku_range)
messages.success(self.request, f"Привязано {link_count} из {count_all} объектов")
return redirect('link_vch_sigma')
def form_invalid(self, form):
return self.render_to_response(self.get_context_data(form=form))

View File

@@ -5,11 +5,11 @@ from . import views
urlpatterns = [ urlpatterns = [
path('3dmap', views.cesium_map, name='3dmap'), path('3dmap', views.CesiumMapView.as_view(), name='3dmap'),
path('2dmap', views.leaflet_map, name='2dmap'), path('2dmap', views.LeafletMapView.as_view(), name='2dmap'),
path('api/footprint-names/<int:sat_id>', views.get_footprints, name="footprint_names"), path('api/footprint-names/<int:sat_id>', views.GetFootprintsView.as_view(), name="footprint_names"),
path('api/transponders/<int:sat_id>', views.get_transponder_on_satid, name='transponders_data'), path('api/transponders/<int:sat_id>', views.GetTransponderOnSatIdView.as_view(), name='transponders_data'),
path('tiles/<str:footprint_name>/<int:z>/<int:x>/<int:y>.png', views.tile_proxy, name='tile_proxy'), path('tiles/<str:footprint_name>/<int:z>/<int:x>/<int:y>.png', views.TileProxyView.as_view(), name='tile_proxy'),
# path('', views.home_page, name='home'), # path('', views.home_page, name='home'),
# path('excel-data', views.load_excel_data, name='load_excel_data'), # path('excel-data', views.load_excel_data, name='load_excel_data'),
# path('satellites', views.add_satellites, name='add_sats'), # path('satellites', views.add_satellites, name='add_sats'),

View File

@@ -5,64 +5,80 @@ from django.core import serializers
from django.http import HttpResponse, HttpResponseNotFound from django.http import HttpResponse, HttpResponseNotFound
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from django.views.decorators.http import require_GET from django.views.decorators.http import require_GET
from django.utils.decorators import method_decorator
from django.views import View
from django.views.generic import TemplateView
from mainapp.models import Satellite from mainapp.models import Satellite
from .models import Transponders from .models import Transponders
from .utils import get_band_names from .utils import get_band_names
def cesium_map(request): class CesiumMapView(TemplateView):
sats = Satellite.objects.all() template_name = 'mapsapp/map3d.html'
return render(request, 'mapsapp/map3d.html', {'sats': sats}) def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['sats'] = Satellite.objects.all()
return context
def get_footprints(request, sat_id): class GetFootprintsView(View):
try: def get(self, request, sat_id):
sat_name = Satellite.objects.get(id=sat_id).name try:
footprint_names = get_band_names(sat_name) sat_name = Satellite.objects.get(id=sat_id).name
footprint_names = get_band_names(sat_name)
return JsonResponse(footprint_names, safe=False)
except Exception as e: return JsonResponse(footprint_names, safe=False)
return JsonResponse({"error": str(e)}, status=400) except Exception as e:
return JsonResponse({"error": str(e)}, status=400)
@require_GET class TileProxyView(View):
@cache_page(60 * 60 * 24) @method_decorator(require_GET)
def tile_proxy(request, footprint_name, z, x, y): @method_decorator(cache_page(60 * 60 * 24)) # Cache for 24 hours
if not footprint_name.replace('-', '').replace('_', '').isalnum(): def dispatch(self, *args, **kwargs):
return HttpResponse("Invalid footprint name", status=400) return super().dispatch(*args, **kwargs)
url = f"https://static.satbeams.com/tiles/{footprint_name}/{z}/{x}/{y}.png" def get(self, request, footprint_name, z, x, y):
if not footprint_name.replace('-', '').replace('_', '').isalnum():
return HttpResponse("Invalid footprint name", status=400)
try: url = f"https://static.satbeams.com/tiles/{footprint_name}/{z}/{x}/{y}.png"
resp = requests.get(url, timeout=10)
if resp.status_code == 200: try:
response = HttpResponse(resp.content, content_type='image/png') resp = requests.get(url, timeout=10)
response["Access-Control-Allow-Origin"] = "*" if resp.status_code == 200:
return response response = HttpResponse(resp.content, content_type='image/png')
else: response["Access-Control-Allow-Origin"] = "*"
return HttpResponseNotFound("Tile not found") return response
except Exception as e: else:
return HttpResponse(f"Proxy error: {e}", status=500) return HttpResponseNotFound("Tile not found")
except Exception as e:
return HttpResponse(f"Proxy error: {e}", status=500)
def leaflet_map(request): class LeafletMapView(TemplateView):
sats = Satellite.objects.all() template_name = 'mapsapp/map2d.html'
trans = Transponders.objects.all()
return render(request, 'mapsapp/map2d.html', {'sats': sats, 'trans': trans}) def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['sats'] = Satellite.objects.all()
context['trans'] = Transponders.objects.all()
return context
def get_transponder_on_satid(request, sat_id): class GetTransponderOnSatIdView(View):
trans = Transponders.objects.filter(sat_id=sat_id) def get(self, request, sat_id):
output = [] trans = Transponders.objects.filter(sat_id=sat_id)
for tran in trans: output = []
output.append( for tran in trans:
{ output.append(
"name": tran.name, {
"frequency": tran.frequency, "name": tran.name,
"frequency_range": tran.frequency_range, "frequency": tran.frequency,
"zone_name": tran.zone_name, "frequency_range": tran.frequency_range,
"polarization": tran.polarization.name "zone_name": tran.zone_name,
} "polarization": tran.polarization.name
) }
if not trans: )
return JsonResponse({'error': 'Объектов не найдено'}, status=400) if not trans:
return JsonResponse({'error': 'Объектов не найдено'}, status=400)
return JsonResponse(output, safe=False) return JsonResponse(output, safe=False)

View File