fixes
This commit is contained in:
29
dbapp/.dockerignore
Normal file
29
dbapp/.dockerignore
Normal 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
10
dbapp/.env.example
Normal 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
51
dbapp/Dockerfile
Normal 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
73
dbapp/Dockerfile.prod
Normal 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"]
|
||||
13
dbapp/dbapp/settings/__init__.py
Normal file
13
dbapp/dbapp/settings/__init__.py
Normal 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 *
|
||||
@@ -12,6 +12,10 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
if os.name == 'nt':
|
||||
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/
|
||||
|
||||
# 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!
|
||||
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
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# 'django_daisy',
|
||||
'dal',
|
||||
'dal_select2',
|
||||
"admin_interface",
|
||||
@@ -64,6 +67,9 @@ INSTALLED_APPS = [
|
||||
'debug_toolbar'
|
||||
]
|
||||
|
||||
# Note: Custom user model is implemented via OneToOneField relationship
|
||||
# AUTH_USER_MODEL = 'mainapp.CustomUser'
|
||||
|
||||
MIDDLEWARE = [
|
||||
"debug_toolbar.middleware.DebugToolbarMiddleware", #Добавил
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
@@ -81,7 +87,9 @@ ROOT_URLCONF = 'dbapp.urls'
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'DIRS': [
|
||||
BASE_DIR / 'templates', # Main project templates directory
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
@@ -101,12 +109,12 @@ WSGI_APPLICATION = 'dbapp.wsgi.application'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
||||
'NAME': 'geodb',
|
||||
'USER': 'geralt',
|
||||
'PASSWORD': '27082025STC',
|
||||
'HOST': 'localhost',
|
||||
'PORT': '5432',
|
||||
'ENGINE': os.getenv('DB_ENGINE', 'django.contrib.gis.db.backends.postgis'),
|
||||
'NAME': os.getenv('DB_NAME', 'db'),
|
||||
'USER': os.getenv('DB_USER', 'user'),
|
||||
'PASSWORD': os.getenv('DB_PASSWORD', 'password'),
|
||||
'HOST': os.getenv('DB_HOST', 'localhost'),
|
||||
'PORT': os.getenv('DB_PORT', '5432'),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +155,7 @@ USE_TZ = True
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
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
|
||||
9
dbapp/dbapp/settings/development.py
Normal file
9
dbapp/dbapp/settings/development.py
Normal 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
|
||||
48
dbapp/dbapp/settings/production.py
Normal file
48
dbapp/dbapp/settings/production.py
Normal 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'
|
||||
@@ -5,16 +5,16 @@ from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.home_page, name='home'),
|
||||
path('excel-data', views.load_excel_data, name='load_excel_data'),
|
||||
path('satellites', views.add_satellites, name='add_sats'),
|
||||
path('api/locations/<int:sat_id>/geojson/', views.get_locations, name='locations_by_id'),
|
||||
path('transponders', views.add_transponders, name='add_trans'),
|
||||
path('csv-data', views.load_raw_csv_data, name='load_csv_data'),
|
||||
path('map-points/', views.show_map_view, name='admin_show_map'),
|
||||
path('cluster/', views.cluster_test, name='cluster'),
|
||||
path('vch-upload/', views.upload_vch_load_from_html, name='vch_load'),
|
||||
path('vch-link/', views.link_vch_sigma, name='link_vch_sigma'),
|
||||
path('', views.HomePageView.as_view(), name='home'),
|
||||
path('excel-data', views.LoadExcelDataView.as_view(), name='load_excel_data'),
|
||||
path('satellites', views.AddSatellitesView.as_view(), name='add_sats'),
|
||||
path('api/locations/<int:sat_id>/geojson/', views.GetLocationsView.as_view(), name='locations_by_id'),
|
||||
path('transponders', views.AddTranspondersView.as_view(), name='add_trans'),
|
||||
path('csv-data', views.LoadCsvDataView.as_view(), name='load_csv_data'),
|
||||
path('map-points/', views.ShowMapView.as_view(), name='admin_show_map'),
|
||||
path('cluster/', views.ClusterTestView.as_view(), name='cluster'),
|
||||
path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'),
|
||||
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
|
||||
# path('upload/', views.upload_file, name='upload_file'),
|
||||
|
||||
]
|
||||
@@ -3,6 +3,10 @@ from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.http import require_GET
|
||||
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
|
||||
from .utils import (
|
||||
fill_data_from_df,
|
||||
@@ -18,97 +22,101 @@ from .clusters import get_clusters
|
||||
from dbapp.settings import BASE_DIR
|
||||
|
||||
|
||||
def add_satellites(request):
|
||||
add_satellite_list()
|
||||
return redirect('home')
|
||||
class AddSatellitesView(View):
|
||||
def get(self, request):
|
||||
add_satellite_list()
|
||||
return redirect('home')
|
||||
|
||||
def add_transponders(request):
|
||||
try:
|
||||
parse_transponders_from_json(BASE_DIR / "transponders.json")
|
||||
except FileNotFoundError:
|
||||
print("Файл не найден")
|
||||
return redirect('home')
|
||||
class AddTranspondersView(View):
|
||||
def get(self, request):
|
||||
try:
|
||||
parse_transponders_from_json(BASE_DIR / "transponders.json")
|
||||
except FileNotFoundError:
|
||||
print("Файл не найден")
|
||||
return redirect('home')
|
||||
|
||||
class HomePageView(TemplateView):
|
||||
template_name = 'mainapp/home.html'
|
||||
|
||||
|
||||
def home_page(request):
|
||||
return render(request, 'mainapp/home.html')
|
||||
class LoadExcelDataView(FormView):
|
||||
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):
|
||||
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']
|
||||
from django.views.generic import View
|
||||
|
||||
try:
|
||||
# Create a temporary file-like object from the uploaded file
|
||||
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)
|
||||
class GetLocationsView(View):
|
||||
def get(self, request, sat_id):
|
||||
locations = ObjItem.objects.filter(id_vch_load__id_satellite=sat_id)
|
||||
if not locations:
|
||||
return JsonResponse({'error': 'Объектов не найдено'}, status=400)
|
||||
|
||||
messages.success(request, f"Данные успешно загружены! Обработано строк: {result}")
|
||||
return redirect('load_excel_data')
|
||||
except Exception as e:
|
||||
messages.error(request, f"Ошибка при обработке файла: {str(e)}")
|
||||
return redirect('load_excel_data')
|
||||
else:
|
||||
form = LoadExcelData()
|
||||
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 render(request, 'mainapp/add_data_from_excel.html', {'form': form})
|
||||
|
||||
|
||||
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({
|
||||
"type": "FeatureCollection",
|
||||
"features": features
|
||||
})
|
||||
|
||||
return JsonResponse({
|
||||
"type": "FeatureCollection",
|
||||
"features": features
|
||||
})
|
||||
class LoadCsvDataView(FormView):
|
||||
template_name = 'mainapp/add_data_from_csv.html'
|
||||
form_class = LoadCsvData
|
||||
|
||||
def load_raw_csv_data(request):
|
||||
if request.method == "POST":
|
||||
form = LoadCsvData(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
uploaded_file = request.FILES['file']
|
||||
try:
|
||||
# Read the file content and pass it directly to the function
|
||||
content = uploaded_file.read()
|
||||
if isinstance(content, bytes):
|
||||
content = content.decode('utf-8')
|
||||
|
||||
get_points_from_csv(content)
|
||||
messages.success(request, f"Данные успешно загружены!")
|
||||
return redirect('load_csv_data')
|
||||
except Exception as e:
|
||||
messages.error(request, f"Ошибка при обработке файла: {str(e)}")
|
||||
return redirect('load_csv_data')
|
||||
|
||||
else:
|
||||
form = LoadCsvData()
|
||||
def form_valid(self, form):
|
||||
uploaded_file = self.request.FILES['file']
|
||||
try:
|
||||
# Read the file content and pass it directly to the function
|
||||
content = uploaded_file.read()
|
||||
if isinstance(content, bytes):
|
||||
content = content.decode('utf-8')
|
||||
|
||||
get_points_from_csv(content)
|
||||
messages.success(self.request, f"Данные успешно загружены!")
|
||||
return redirect('load_csv_data')
|
||||
except Exception as e:
|
||||
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
|
||||
return redirect('load_csv_data')
|
||||
|
||||
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):
|
||||
# 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 render(request, 'mainapp/add_data_from_csv.html')
|
||||
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"]
|
||||
})
|
||||
|
||||
# Преобразуем в список словарей для удобства в шаблоне
|
||||
groups = [
|
||||
{
|
||||
"name": name,
|
||||
"points": coords_list
|
||||
}
|
||||
for name, coords_list in grouped.items()
|
||||
]
|
||||
@method_decorator(staff_member_required, name='dispatch')
|
||||
class ShowMapView(UserPassesTestMixin, View):
|
||||
def test_func(self):
|
||||
return self.request.user.is_staff
|
||||
|
||||
|
||||
context = {
|
||||
'groups': groups,
|
||||
}
|
||||
return render(request, 'admin/map_custom.html', context)
|
||||
|
||||
|
||||
def cluster_test(request):
|
||||
objs = ObjItem.objects.filter(name__icontains="! Astra 4A 12654,040 [1,962] МГц H")
|
||||
coords = []
|
||||
for obj in objs:
|
||||
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}")
|
||||
def get(self, 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:
|
||||
messages.error(request, "Форма заполнена некорректно.")
|
||||
else:
|
||||
form = UploadFileForm()
|
||||
|
||||
return render(request, 'mainapp/upload_html.html', {'form': form})
|
||||
return redirect('admin')
|
||||
grouped = defaultdict(list)
|
||||
for p in points:
|
||||
grouped[p["name"]].append({
|
||||
'point': p["point"],
|
||||
'frequency': p["freq"]
|
||||
})
|
||||
|
||||
# Преобразуем в список словарей для удобства в шаблоне
|
||||
groups = [
|
||||
{
|
||||
"name": name,
|
||||
"points": coords_list
|
||||
}
|
||||
for name, coords_list in grouped.items()
|
||||
]
|
||||
|
||||
|
||||
def link_vch_sigma(request):
|
||||
if request.method == 'POST':
|
||||
form = VchLinkForm(request.POST)
|
||||
if form.is_valid():
|
||||
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(request, f"Привязано {link_count} из {count_all} объектов")
|
||||
return redirect('link_vch_sigma')
|
||||
else:
|
||||
form = VchLinkForm()
|
||||
|
||||
return render(request, 'mainapp/link_vch.html', {'form': form})
|
||||
context = {
|
||||
'groups': groups,
|
||||
}
|
||||
return render(request, 'admin/map_custom.html', context)
|
||||
|
||||
|
||||
class ClusterTestView(View):
|
||||
def get(self, request):
|
||||
objs = ObjItem.objects.filter(name__icontains="! Astra 4A 12654,040 [1,962] МГц H")
|
||||
coords = []
|
||||
for obj in objs:
|
||||
coords.append((obj.id_geo.coords[1], obj.id_geo.coords[0]))
|
||||
get_clusters(coords)
|
||||
|
||||
return JsonResponse({"success": "ок"})
|
||||
|
||||
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))
|
||||
@@ -5,11 +5,11 @@ from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('3dmap', views.cesium_map, name='3dmap'),
|
||||
path('2dmap', views.leaflet_map, name='2dmap'),
|
||||
path('api/footprint-names/<int:sat_id>', views.get_footprints, name="footprint_names"),
|
||||
path('api/transponders/<int:sat_id>', views.get_transponder_on_satid, name='transponders_data'),
|
||||
path('tiles/<str:footprint_name>/<int:z>/<int:x>/<int:y>.png', views.tile_proxy, name='tile_proxy'),
|
||||
path('3dmap', views.CesiumMapView.as_view(), name='3dmap'),
|
||||
path('2dmap', views.LeafletMapView.as_view(), name='2dmap'),
|
||||
path('api/footprint-names/<int:sat_id>', views.GetFootprintsView.as_view(), name="footprint_names"),
|
||||
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.TileProxyView.as_view(), name='tile_proxy'),
|
||||
# path('', views.home_page, name='home'),
|
||||
# path('excel-data', views.load_excel_data, name='load_excel_data'),
|
||||
# path('satellites', views.add_satellites, name='add_sats'),
|
||||
|
||||
@@ -5,64 +5,80 @@ from django.core import serializers
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from django.views.decorators.cache import cache_page
|
||||
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 .models import Transponders
|
||||
from .utils import get_band_names
|
||||
|
||||
def cesium_map(request):
|
||||
sats = Satellite.objects.all()
|
||||
class CesiumMapView(TemplateView):
|
||||
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):
|
||||
try:
|
||||
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({"error": str(e)}, status=400)
|
||||
class GetFootprintsView(View):
|
||||
def get(self, request, sat_id):
|
||||
try:
|
||||
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({"error": str(e)}, status=400)
|
||||
|
||||
|
||||
@require_GET
|
||||
@cache_page(60 * 60 * 24)
|
||||
def tile_proxy(request, footprint_name, z, x, y):
|
||||
if not footprint_name.replace('-', '').replace('_', '').isalnum():
|
||||
return HttpResponse("Invalid footprint name", status=400)
|
||||
class TileProxyView(View):
|
||||
@method_decorator(require_GET)
|
||||
@method_decorator(cache_page(60 * 60 * 24)) # Cache for 24 hours
|
||||
def dispatch(self, *args, **kwargs):
|
||||
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:
|
||||
resp = requests.get(url, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
response = HttpResponse(resp.content, content_type='image/png')
|
||||
response["Access-Control-Allow-Origin"] = "*"
|
||||
return response
|
||||
else:
|
||||
return HttpResponseNotFound("Tile not found")
|
||||
except Exception as e:
|
||||
return HttpResponse(f"Proxy error: {e}", status=500)
|
||||
url = f"https://static.satbeams.com/tiles/{footprint_name}/{z}/{x}/{y}.png"
|
||||
|
||||
try:
|
||||
resp = requests.get(url, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
response = HttpResponse(resp.content, content_type='image/png')
|
||||
response["Access-Control-Allow-Origin"] = "*"
|
||||
return response
|
||||
else:
|
||||
return HttpResponseNotFound("Tile not found")
|
||||
except Exception as e:
|
||||
return HttpResponse(f"Proxy error: {e}", status=500)
|
||||
|
||||
def leaflet_map(request):
|
||||
sats = Satellite.objects.all()
|
||||
trans = Transponders.objects.all()
|
||||
return render(request, 'mapsapp/map2d.html', {'sats': sats, 'trans': trans})
|
||||
class LeafletMapView(TemplateView):
|
||||
template_name = 'mapsapp/map2d.html'
|
||||
|
||||
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):
|
||||
trans = Transponders.objects.filter(sat_id=sat_id)
|
||||
output = []
|
||||
for tran in trans:
|
||||
output.append(
|
||||
{
|
||||
"name": tran.name,
|
||||
"frequency": tran.frequency,
|
||||
"frequency_range": tran.frequency_range,
|
||||
"zone_name": tran.zone_name,
|
||||
"polarization": tran.polarization.name
|
||||
}
|
||||
)
|
||||
if not trans:
|
||||
return JsonResponse({'error': 'Объектов не найдено'}, status=400)
|
||||
class GetTransponderOnSatIdView(View):
|
||||
def get(self, request, sat_id):
|
||||
trans = Transponders.objects.filter(sat_id=sat_id)
|
||||
output = []
|
||||
for tran in trans:
|
||||
output.append(
|
||||
{
|
||||
"name": tran.name,
|
||||
"frequency": tran.frequency,
|
||||
"frequency_range": tran.frequency_range,
|
||||
"zone_name": tran.zone_name,
|
||||
"polarization": tran.polarization.name
|
||||
}
|
||||
)
|
||||
if not trans:
|
||||
return JsonResponse({'error': 'Объектов не найдено'}, status=400)
|
||||
|
||||
return JsonResponse(output, safe=False)
|
||||
return JsonResponse(output, safe=False)
|
||||
36
dbapp/pyproject.toml
Normal file
36
dbapp/pyproject.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[project]
|
||||
name = "datastorage"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"aiosqlite>=0.21.0",
|
||||
"bcrypt>=5.0.0",
|
||||
"django>=5.2.7",
|
||||
"django-admin-interface>=0.30.1",
|
||||
"django-admin-multiple-choice-list-filter>=0.1.1",
|
||||
"django-admin-rangefilter>=0.13.3",
|
||||
"django-autocomplete-light>=3.12.1",
|
||||
"django-daisy>=1.1.2",
|
||||
"django-debug-toolbar>=6.0.0",
|
||||
"django-dynamic-raw-id>=4.4",
|
||||
"django-import-export>=4.3.10",
|
||||
"django-leaflet>=0.32.0",
|
||||
"django-map-widgets>=0.5.1",
|
||||
"django-more-admin-filters>=1.13",
|
||||
"gdal",
|
||||
"lxml>=6.0.2",
|
||||
"matplotlib>=3.10.7",
|
||||
"numpy>=2.3.3",
|
||||
"openpyxl>=3.1.5",
|
||||
"pandas>=2.3.3",
|
||||
"psycopg>=3.2.10",
|
||||
"redis>=6.4.0",
|
||||
"requests>=2.32.5",
|
||||
"scikit-learn>=1.7.2",
|
||||
"setuptools>=80.9.0",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
gdal = { path = "gdal-3.10.2-cp313-cp313-win_amd64.whl" }
|
||||
1058
dbapp/uv.lock
generated
Normal file
1058
dbapp/uv.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user