fixes
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
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
|
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
|
||||||
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 = [
|
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'),
|
||||||
|
|
||||||
]
|
]
|
||||||
@@ -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))
|
||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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)
|
||||||
0
uv.lock → dbapp/uv.lock
generated
0
uv.lock → dbapp/uv.lock
generated
Reference in New Issue
Block a user