Реструктуризация views
This commit is contained in:
75
dbapp/mainapp/views/README.md
Normal file
75
dbapp/mainapp/views/README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Views Module Structure
|
||||
|
||||
This directory contains the refactored views from the original monolithic `views.py` file.
|
||||
|
||||
## File Organization
|
||||
|
||||
### `__init__.py`
|
||||
Central import file that exports all views for easy access. This allows other modules to import views using:
|
||||
```python
|
||||
from mainapp.views import ObjItemListView, custom_logout
|
||||
```
|
||||
|
||||
### `base.py`
|
||||
Basic views and utilities:
|
||||
- `ActionsPageView` - Displays the actions page
|
||||
- `custom_logout()` - Custom logout function
|
||||
|
||||
### `objitem.py`
|
||||
ObjItem CRUD operations and related views:
|
||||
- `ObjItemListView` - List view with filtering and pagination
|
||||
- `ObjItemFormView` - Base class for create/update operations
|
||||
- `ObjItemCreateView` - Create new ObjItem
|
||||
- `ObjItemUpdateView` - Update existing ObjItem
|
||||
- `ObjItemDeleteView` - Delete ObjItem
|
||||
- `ObjItemDetailView` - Read-only detail view
|
||||
- `DeleteSelectedObjectsView` - Bulk delete operation
|
||||
|
||||
### `data_import.py`
|
||||
Data import views for various formats:
|
||||
- `AddSatellitesView` - Add satellites to database
|
||||
- `AddTranspondersView` - Upload and parse transponder data from XML
|
||||
- `LoadExcelDataView` - Load data from Excel files
|
||||
- `LoadCsvDataView` - Load data from CSV files
|
||||
- `UploadVchLoadView` - Upload VCH load data from HTML
|
||||
- `LinkVchSigmaView` - Link VCH data with Sigma parameters
|
||||
- `ProcessKubsatView` - Process Kubsat event data
|
||||
|
||||
### `api.py`
|
||||
API endpoints for AJAX requests:
|
||||
- `GetLocationsView` - Get locations by satellite ID in GeoJSON format
|
||||
- `LyngsatDataAPIView` - Get LyngSat source data
|
||||
- `SigmaParameterDataAPIView` - Get SigmaParameter data
|
||||
- `SourceObjItemsAPIView` - Get ObjItems related to a Source
|
||||
- `LyngsatTaskStatusAPIView` - Get Celery task status
|
||||
|
||||
### `lyngsat.py`
|
||||
LyngSat related views:
|
||||
- `LinkLyngsatSourcesView` - Link LyngSat sources to objects
|
||||
- `FillLyngsatDataView` - Fill data from Lyngsat website
|
||||
- `LyngsatTaskStatusView` - Track Lyngsat data filling task status
|
||||
- `ClearLyngsatCacheView` - Clear LyngSat cache
|
||||
|
||||
### `source.py`
|
||||
Source related views:
|
||||
- `SourceListView` - List view for Source objects with filtering
|
||||
|
||||
### `map.py`
|
||||
Map related views:
|
||||
- `ShowMapView` - Display objects on map (admin interface)
|
||||
- `ShowSelectedObjectsMapView` - Display selected objects on map
|
||||
- `ClusterTestView` - Test view for clustering functionality
|
||||
|
||||
## Migration Notes
|
||||
|
||||
The original `views.py` has been renamed to `views_old.py` as a backup. All imports have been updated in:
|
||||
- `dbapp/mainapp/urls.py`
|
||||
- `dbapp/dbapp/urls.py`
|
||||
|
||||
## Benefits of This Structure
|
||||
|
||||
1. **Better Organization** - Related views are grouped together
|
||||
2. **Easier Maintenance** - Smaller files are easier to navigate and modify
|
||||
3. **Clear Responsibilities** - Each file has a specific purpose
|
||||
4. **Improved Testability** - Easier to write focused unit tests
|
||||
5. **Better Collaboration** - Multiple developers can work on different files without conflicts
|
||||
72
dbapp/mainapp/views/__init__.py
Normal file
72
dbapp/mainapp/views/__init__.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Import all views for easy access
|
||||
from .base import ActionsPageView, custom_logout
|
||||
from .objitem import (
|
||||
ObjItemListView,
|
||||
ObjItemCreateView,
|
||||
ObjItemUpdateView,
|
||||
ObjItemDeleteView,
|
||||
ObjItemDetailView,
|
||||
DeleteSelectedObjectsView,
|
||||
)
|
||||
from .data_import import (
|
||||
AddSatellitesView,
|
||||
AddTranspondersView,
|
||||
LoadExcelDataView,
|
||||
LoadCsvDataView,
|
||||
UploadVchLoadView,
|
||||
LinkVchSigmaView,
|
||||
ProcessKubsatView,
|
||||
)
|
||||
from .api import (
|
||||
GetLocationsView,
|
||||
LyngsatDataAPIView,
|
||||
SigmaParameterDataAPIView,
|
||||
SourceObjItemsAPIView,
|
||||
LyngsatTaskStatusAPIView,
|
||||
)
|
||||
from .lyngsat import (
|
||||
LinkLyngsatSourcesView,
|
||||
FillLyngsatDataView,
|
||||
LyngsatTaskStatusView,
|
||||
ClearLyngsatCacheView,
|
||||
)
|
||||
from .source import SourceListView
|
||||
from .map import ShowMapView, ShowSelectedObjectsMapView, ClusterTestView
|
||||
|
||||
__all__ = [
|
||||
# Base
|
||||
'ActionsPageView',
|
||||
'custom_logout',
|
||||
# ObjItem
|
||||
'ObjItemListView',
|
||||
'ObjItemCreateView',
|
||||
'ObjItemUpdateView',
|
||||
'ObjItemDeleteView',
|
||||
'ObjItemDetailView',
|
||||
'DeleteSelectedObjectsView',
|
||||
# Data Import
|
||||
'AddSatellitesView',
|
||||
'AddTranspondersView',
|
||||
'LoadExcelDataView',
|
||||
'LoadCsvDataView',
|
||||
'UploadVchLoadView',
|
||||
'LinkVchSigmaView',
|
||||
'ProcessKubsatView',
|
||||
# API
|
||||
'GetLocationsView',
|
||||
'LyngsatDataAPIView',
|
||||
'SigmaParameterDataAPIView',
|
||||
'SourceObjItemsAPIView',
|
||||
'LyngsatTaskStatusAPIView',
|
||||
# LyngSat
|
||||
'LinkLyngsatSourcesView',
|
||||
'FillLyngsatDataView',
|
||||
'LyngsatTaskStatusView',
|
||||
'ClearLyngsatCacheView',
|
||||
# Source
|
||||
'SourceListView',
|
||||
# Map
|
||||
'ShowMapView',
|
||||
'ShowSelectedObjectsMapView',
|
||||
'ClusterTestView',
|
||||
]
|
||||
301
dbapp/mainapp/views/api.py
Normal file
301
dbapp/mainapp/views/api.py
Normal file
@@ -0,0 +1,301 @@
|
||||
"""
|
||||
API endpoints for AJAX requests and data retrieval.
|
||||
"""
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import JsonResponse
|
||||
from django.utils import timezone
|
||||
from django.views import View
|
||||
|
||||
from ..models import ObjItem
|
||||
|
||||
|
||||
class GetLocationsView(LoginRequiredMixin, View):
|
||||
"""API endpoint for getting locations by satellite ID in GeoJSON format."""
|
||||
|
||||
def get(self, request, sat_id):
|
||||
locations = (
|
||||
ObjItem.objects.filter(parameter_obj__id_satellite=sat_id)
|
||||
.select_related(
|
||||
"geo_obj",
|
||||
"parameter_obj",
|
||||
"parameter_obj__polarization",
|
||||
)
|
||||
)
|
||||
|
||||
if not locations.exists():
|
||||
return JsonResponse({"error": "Объектов не найдено"}, status=404)
|
||||
|
||||
features = []
|
||||
for loc in locations:
|
||||
if not hasattr(loc, "geo_obj") or not loc.geo_obj or not loc.geo_obj.coords:
|
||||
continue
|
||||
|
||||
param = getattr(loc, 'parameter_obj', None)
|
||||
if not param:
|
||||
continue
|
||||
|
||||
features.append(
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [loc.geo_obj.coords[0], loc.geo_obj.coords[1]],
|
||||
},
|
||||
"properties": {
|
||||
"pol": param.polarization.name if param.polarization else "-",
|
||||
"freq": param.frequency * 1000000 if param.frequency else 0,
|
||||
"name": loc.name or "-",
|
||||
"id": loc.geo_obj.id,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return JsonResponse({"type": "FeatureCollection", "features": features})
|
||||
|
||||
|
||||
class LyngsatDataAPIView(LoginRequiredMixin, View):
|
||||
"""API endpoint for getting LyngSat source data."""
|
||||
|
||||
def get(self, request, lyngsat_id):
|
||||
from lyngsatapp.models import LyngSat
|
||||
|
||||
try:
|
||||
lyngsat = LyngSat.objects.select_related(
|
||||
'id_satellite',
|
||||
'polarization',
|
||||
'modulation',
|
||||
'standard'
|
||||
).get(id=lyngsat_id)
|
||||
|
||||
# Format date with local timezone
|
||||
last_update_str = '-'
|
||||
if lyngsat.last_update:
|
||||
local_time = timezone.localtime(lyngsat.last_update)
|
||||
last_update_str = local_time.strftime("%d.%m.%Y")
|
||||
|
||||
data = {
|
||||
'id': lyngsat.id,
|
||||
'satellite': lyngsat.id_satellite.name if lyngsat.id_satellite else '-',
|
||||
'frequency': f"{lyngsat.frequency:.3f}" if lyngsat.frequency else '-',
|
||||
'polarization': lyngsat.polarization.name if lyngsat.polarization else '-',
|
||||
'modulation': lyngsat.modulation.name if lyngsat.modulation else '-',
|
||||
'standard': lyngsat.standard.name if lyngsat.standard else '-',
|
||||
'sym_velocity': f"{lyngsat.sym_velocity:.0f}" if lyngsat.sym_velocity else '-',
|
||||
'fec': lyngsat.fec or '-',
|
||||
'channel_info': lyngsat.channel_info or '-',
|
||||
'last_update': last_update_str,
|
||||
'url': lyngsat.url or None,
|
||||
}
|
||||
|
||||
return JsonResponse(data)
|
||||
except LyngSat.DoesNotExist:
|
||||
return JsonResponse({'error': 'Источник LyngSat не найден'}, status=404)
|
||||
except Exception as e:
|
||||
return JsonResponse({'error': str(e)}, status=500)
|
||||
|
||||
|
||||
class SigmaParameterDataAPIView(LoginRequiredMixin, View):
|
||||
"""API endpoint for getting SigmaParameter data."""
|
||||
|
||||
def get(self, request, parameter_id):
|
||||
from ..models import Parameter
|
||||
|
||||
try:
|
||||
parameter = Parameter.objects.select_related(
|
||||
'id_satellite',
|
||||
'polarization',
|
||||
'modulation',
|
||||
'standard'
|
||||
).prefetch_related(
|
||||
'sigma_parameter__mark',
|
||||
'sigma_parameter__id_satellite',
|
||||
'sigma_parameter__polarization',
|
||||
'sigma_parameter__modulation',
|
||||
'sigma_parameter__standard'
|
||||
).get(id=parameter_id)
|
||||
|
||||
# Get all related SigmaParameter
|
||||
sigma_params = parameter.sigma_parameter.all()
|
||||
|
||||
sigma_data = []
|
||||
for sigma in sigma_params:
|
||||
# Get marks
|
||||
marks = []
|
||||
for mark in sigma.mark.all().order_by('-timestamp'):
|
||||
mark_str = '+' if mark.mark else '-'
|
||||
date_str = '-'
|
||||
if mark.timestamp:
|
||||
local_time = timezone.localtime(mark.timestamp)
|
||||
date_str = local_time.strftime("%d.%m.%Y %H:%M")
|
||||
marks.append({
|
||||
'mark': mark_str,
|
||||
'date': date_str
|
||||
})
|
||||
|
||||
# Format start and end dates
|
||||
datetime_begin_str = '-'
|
||||
if sigma.datetime_begin:
|
||||
local_time = timezone.localtime(sigma.datetime_begin)
|
||||
datetime_begin_str = local_time.strftime("%d.%m.%Y %H:%M")
|
||||
|
||||
datetime_end_str = '-'
|
||||
if sigma.datetime_end:
|
||||
local_time = timezone.localtime(sigma.datetime_end)
|
||||
datetime_end_str = local_time.strftime("%d.%m.%Y %H:%M")
|
||||
|
||||
sigma_data.append({
|
||||
'id': sigma.id,
|
||||
'satellite': sigma.id_satellite.name if sigma.id_satellite else '-',
|
||||
'frequency': f"{sigma.frequency:.3f}" if sigma.frequency else '-',
|
||||
'transfer_frequency': f"{sigma.transfer_frequency:.3f}" if sigma.transfer_frequency else '-',
|
||||
'freq_range': f"{sigma.freq_range:.3f}" if sigma.freq_range else '-',
|
||||
'polarization': sigma.polarization.name if sigma.polarization else '-',
|
||||
'modulation': sigma.modulation.name if sigma.modulation else '-',
|
||||
'standard': sigma.standard.name if sigma.standard else '-',
|
||||
'bod_velocity': f"{sigma.bod_velocity:.0f}" if sigma.bod_velocity else '-',
|
||||
'snr': f"{sigma.snr:.1f}" if sigma.snr is not None else '-',
|
||||
'power': f"{sigma.power:.1f}" if sigma.power is not None else '-',
|
||||
'status': sigma.status or '-',
|
||||
'packets': 'Да' if sigma.packets else 'Нет' if sigma.packets is not None else '-',
|
||||
'datetime_begin': datetime_begin_str,
|
||||
'datetime_end': datetime_end_str,
|
||||
'marks': marks
|
||||
})
|
||||
|
||||
return JsonResponse({
|
||||
'parameter_id': parameter.id,
|
||||
'sigma_parameters': sigma_data
|
||||
})
|
||||
except Parameter.DoesNotExist:
|
||||
return JsonResponse({'error': 'Parameter не найден'}, status=404)
|
||||
except Exception as e:
|
||||
return JsonResponse({'error': str(e)}, status=500)
|
||||
|
||||
|
||||
class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
||||
"""API endpoint for getting ObjItems related to a Source."""
|
||||
|
||||
def get(self, request, source_id):
|
||||
from ..models import Source
|
||||
|
||||
try:
|
||||
# Load Source with prefetch_related for ObjItem
|
||||
source = Source.objects.prefetch_related(
|
||||
'source_objitems',
|
||||
'source_objitems__parameter_obj',
|
||||
'source_objitems__parameter_obj__id_satellite',
|
||||
'source_objitems__parameter_obj__polarization',
|
||||
'source_objitems__parameter_obj__modulation',
|
||||
'source_objitems__geo_obj'
|
||||
).get(id=source_id)
|
||||
|
||||
# Get all related ObjItems, sorted by created_at
|
||||
objitems = source.source_objitems.all().order_by('created_at')
|
||||
|
||||
objitems_data = []
|
||||
for objitem in objitems:
|
||||
# Get parameter data
|
||||
param = getattr(objitem, 'parameter_obj', None)
|
||||
satellite_name = '-'
|
||||
frequency = '-'
|
||||
freq_range = '-'
|
||||
polarization = '-'
|
||||
bod_velocity = '-'
|
||||
modulation = '-'
|
||||
snr = '-'
|
||||
|
||||
if param:
|
||||
if hasattr(param, 'id_satellite') and param.id_satellite:
|
||||
satellite_name = param.id_satellite.name
|
||||
frequency = f"{param.frequency:.3f}" if param.frequency is not None else '-'
|
||||
freq_range = f"{param.freq_range:.3f}" if param.freq_range is not None else '-'
|
||||
if hasattr(param, 'polarization') and param.polarization:
|
||||
polarization = param.polarization.name
|
||||
bod_velocity = f"{param.bod_velocity:.0f}" if param.bod_velocity is not None else '-'
|
||||
if hasattr(param, 'modulation') and param.modulation:
|
||||
modulation = param.modulation.name
|
||||
snr = f"{param.snr:.0f}" if param.snr is not None else '-'
|
||||
|
||||
# Get geo data
|
||||
geo_timestamp = '-'
|
||||
geo_location = '-'
|
||||
geo_coords = '-'
|
||||
|
||||
if hasattr(objitem, 'geo_obj') and objitem.geo_obj:
|
||||
if objitem.geo_obj.timestamp:
|
||||
local_time = timezone.localtime(objitem.geo_obj.timestamp)
|
||||
geo_timestamp = local_time.strftime("%d.%m.%Y %H:%M")
|
||||
|
||||
geo_location = objitem.geo_obj.location or '-'
|
||||
|
||||
if objitem.geo_obj.coords:
|
||||
longitude = objitem.geo_obj.coords.coords[0]
|
||||
latitude = objitem.geo_obj.coords.coords[1]
|
||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
||||
geo_coords = f"{lat} {lon}"
|
||||
|
||||
objitems_data.append({
|
||||
'id': objitem.id,
|
||||
'name': objitem.name or '-',
|
||||
'satellite_name': satellite_name,
|
||||
'frequency': frequency,
|
||||
'freq_range': freq_range,
|
||||
'polarization': polarization,
|
||||
'bod_velocity': bod_velocity,
|
||||
'modulation': modulation,
|
||||
'snr': snr,
|
||||
'geo_timestamp': geo_timestamp,
|
||||
'geo_location': geo_location,
|
||||
'geo_coords': geo_coords
|
||||
})
|
||||
|
||||
return JsonResponse({
|
||||
'source_id': source_id,
|
||||
'objitems': objitems_data
|
||||
})
|
||||
except Source.DoesNotExist:
|
||||
return JsonResponse({'error': 'Источник не найден'}, status=404)
|
||||
except Exception as e:
|
||||
return JsonResponse({'error': str(e)}, status=500)
|
||||
|
||||
|
||||
class LyngsatTaskStatusAPIView(LoginRequiredMixin, View):
|
||||
"""API endpoint for getting Celery task status."""
|
||||
|
||||
def get(self, request, task_id):
|
||||
from celery.result import AsyncResult
|
||||
from django.core.cache import cache
|
||||
|
||||
task = AsyncResult(task_id)
|
||||
|
||||
response_data = {
|
||||
'task_id': task_id,
|
||||
'state': task.state,
|
||||
'result': None,
|
||||
'error': None
|
||||
}
|
||||
|
||||
if task.state == 'PENDING':
|
||||
response_data['status'] = 'Задача в очереди...'
|
||||
elif task.state == 'PROGRESS':
|
||||
response_data['status'] = task.info.get('status', '')
|
||||
response_data['current'] = task.info.get('current', 0)
|
||||
response_data['total'] = task.info.get('total', 1)
|
||||
response_data['percent'] = int((task.info.get('current', 0) / task.info.get('total', 1)) * 100)
|
||||
elif task.state == 'SUCCESS':
|
||||
# Get result from cache
|
||||
result = cache.get(f'lyngsat_task_{task_id}')
|
||||
if result:
|
||||
response_data['result'] = result
|
||||
response_data['status'] = 'Задача завершена успешно'
|
||||
else:
|
||||
response_data['result'] = task.result
|
||||
response_data['status'] = 'Задача завершена'
|
||||
elif task.state == 'FAILURE':
|
||||
response_data['status'] = 'Ошибка при выполнении задачи'
|
||||
response_data['error'] = str(task.info)
|
||||
else:
|
||||
response_data['status'] = task.state
|
||||
|
||||
return JsonResponse(response_data)
|
||||
22
dbapp/mainapp/views/base.py
Normal file
22
dbapp/mainapp/views/base.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Base views and utilities.
|
||||
"""
|
||||
from django.contrib.auth import logout
|
||||
from django.shortcuts import redirect, render
|
||||
from django.views import View
|
||||
|
||||
|
||||
class ActionsPageView(View):
|
||||
"""View for displaying the actions page."""
|
||||
|
||||
def get(self, request):
|
||||
if request.user.is_authenticated:
|
||||
return render(request, "mainapp/actions.html")
|
||||
else:
|
||||
return render(request, "mainapp/login_required.html")
|
||||
|
||||
|
||||
def custom_logout(request):
|
||||
"""Custom logout view."""
|
||||
logout(request)
|
||||
return redirect("mainapp:home")
|
||||
205
dbapp/mainapp/views/data_import.py
Normal file
205
dbapp/mainapp/views/data_import.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
Data import views (Excel, CSV, Transponders, VCH, etc.).
|
||||
"""
|
||||
from io import BytesIO
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.views import View
|
||||
from django.views.generic import FormView
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from ..forms import (
|
||||
LoadCsvData,
|
||||
LoadExcelData,
|
||||
NewEventForm,
|
||||
UploadFileForm,
|
||||
UploadVchLoad,
|
||||
VchLinkForm,
|
||||
)
|
||||
from ..mixins import FormMessageMixin
|
||||
from ..utils import (
|
||||
add_satellite_list,
|
||||
compare_and_link_vch_load,
|
||||
fill_data_from_df,
|
||||
get_points_from_csv,
|
||||
get_vch_load_from_html,
|
||||
kub_report,
|
||||
)
|
||||
from mapsapp.utils import parse_transponders_from_xml
|
||||
|
||||
|
||||
class AddSatellitesView(LoginRequiredMixin, View):
|
||||
"""View for adding satellites to the database."""
|
||||
|
||||
def get(self, request):
|
||||
add_satellite_list()
|
||||
return redirect("mainapp:home")
|
||||
|
||||
|
||||
class AddTranspondersView(LoginRequiredMixin, FormMessageMixin, FormView):
|
||||
"""View for uploading and parsing transponder data from XML."""
|
||||
|
||||
template_name = "mainapp/transponders_upload.html"
|
||||
form_class = UploadFileForm
|
||||
success_message = "Файл успешно обработан"
|
||||
error_message = "Форма заполнена некорректно"
|
||||
|
||||
def form_valid(self, form):
|
||||
uploaded_file = self.request.FILES["file"]
|
||||
try:
|
||||
content = uploaded_file.read()
|
||||
parse_transponders_from_xml(BytesIO(content))
|
||||
except ValueError as e:
|
||||
messages.error(self.request, f"Ошибка при чтении таблиц: {e}")
|
||||
return redirect("mainapp:add_trans")
|
||||
except Exception as e:
|
||||
messages.error(self.request, f"Неизвестная ошибка: {e}")
|
||||
return redirect("mainapp:add_trans")
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("mainapp:add_trans")
|
||||
|
||||
|
||||
class LoadExcelDataView(LoginRequiredMixin, FormMessageMixin, FormView):
|
||||
"""View for loading data from Excel files."""
|
||||
|
||||
template_name = "mainapp/add_data_from_excel.html"
|
||||
form_class = LoadExcelData
|
||||
error_message = "Форма заполнена некорректно"
|
||||
|
||||
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, self.request.user.customuser)
|
||||
|
||||
messages.success(
|
||||
self.request, f"Данные успешно загружены! Обработано строк: {result}"
|
||||
)
|
||||
except Exception as e:
|
||||
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
|
||||
|
||||
return redirect("mainapp:load_excel_data")
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("mainapp:load_excel_data")
|
||||
|
||||
|
||||
class LoadCsvDataView(LoginRequiredMixin, FormMessageMixin, FormView):
|
||||
"""View for loading data from CSV files."""
|
||||
|
||||
template_name = "mainapp/add_data_from_csv.html"
|
||||
form_class = LoadCsvData
|
||||
success_message = "Данные успешно загружены!"
|
||||
error_message = "Форма заполнена некорректно"
|
||||
|
||||
def form_valid(self, form):
|
||||
uploaded_file = self.request.FILES["file"]
|
||||
try:
|
||||
content = uploaded_file.read()
|
||||
if isinstance(content, bytes):
|
||||
content = content.decode("utf-8")
|
||||
|
||||
get_points_from_csv(content, self.request.user.customuser)
|
||||
except Exception as e:
|
||||
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
|
||||
return redirect("mainapp:load_csv_data")
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("mainapp:load_csv_data")
|
||||
|
||||
|
||||
class UploadVchLoadView(LoginRequiredMixin, FormMessageMixin, FormView):
|
||||
"""View for uploading VCH load data from HTML files."""
|
||||
|
||||
template_name = "mainapp/upload_html.html"
|
||||
form_class = UploadVchLoad
|
||||
success_message = "Файл успешно обработан"
|
||||
error_message = "Форма заполнена некорректно"
|
||||
|
||||
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)
|
||||
except ValueError as e:
|
||||
messages.error(self.request, f"Ошибка при чтении таблиц: {e}")
|
||||
return redirect("mainapp:vch_load")
|
||||
except Exception as e:
|
||||
messages.error(self.request, f"Неизвестная ошибка: {e}")
|
||||
return redirect("mainapp:vch_load")
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("mainapp:vch_load")
|
||||
|
||||
|
||||
class LinkVchSigmaView(LoginRequiredMixin, FormView):
|
||||
"""View for linking VCH data with Sigma parameters."""
|
||||
|
||||
template_name = "mainapp/link_vch.html"
|
||||
form_class = VchLinkForm
|
||||
|
||||
def form_valid(self, form):
|
||||
# value1 is no longer used - frequency tolerance is determined automatically
|
||||
freq_range = form.cleaned_data["value2"]
|
||||
sat_id = form.cleaned_data["sat_choice"]
|
||||
|
||||
# Pass 0 for eps_freq and ku_range as they are not used
|
||||
count_all, link_count = compare_and_link_vch_load(sat_id, 0, freq_range, 0)
|
||||
|
||||
messages.success(
|
||||
self.request, f"Привязано {link_count} из {count_all} объектов"
|
||||
)
|
||||
return redirect("mainapp:link_vch_sigma")
|
||||
|
||||
def form_invalid(self, form):
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
|
||||
class ProcessKubsatView(LoginRequiredMixin, FormMessageMixin, FormView):
|
||||
"""View for processing Kubsat event data."""
|
||||
|
||||
template_name = "mainapp/process_kubsat.html"
|
||||
form_class = NewEventForm
|
||||
error_message = "Форма заполнена некорректно"
|
||||
|
||||
def form_valid(self, form):
|
||||
uploaded_file = self.request.FILES["file"]
|
||||
try:
|
||||
content = uploaded_file.read()
|
||||
df = kub_report(BytesIO(content))
|
||||
output = BytesIO()
|
||||
with pd.ExcelWriter(output, engine="openpyxl") as writer:
|
||||
df.to_excel(writer, index=False, sheet_name="Результат")
|
||||
output.seek(0)
|
||||
|
||||
response = HttpResponse(
|
||||
output.getvalue(),
|
||||
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
)
|
||||
response["Content-Disposition"] = (
|
||||
'attachment; filename="kubsat_report.xlsx"'
|
||||
)
|
||||
|
||||
messages.success(self.request, "Событие успешно обработано!")
|
||||
return response
|
||||
except Exception as e:
|
||||
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
|
||||
return redirect("mainapp:kubsat_excel")
|
||||
161
dbapp/mainapp/views/lyngsat.py
Normal file
161
dbapp/mainapp/views/lyngsat.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""
|
||||
LyngSat related views for data synchronization and linking.
|
||||
"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse_lazy
|
||||
from django.views import View
|
||||
from django.views.generic import FormView
|
||||
|
||||
from ..forms import FillLyngsatDataForm, LinkLyngsatForm
|
||||
from ..mixins import FormMessageMixin
|
||||
from ..models import ObjItem
|
||||
|
||||
|
||||
class LinkLyngsatSourcesView(LoginRequiredMixin, FormMessageMixin, FormView):
|
||||
"""View for linking LyngSat sources to objects."""
|
||||
|
||||
template_name = "mainapp/link_lyngsat.html"
|
||||
form_class = LinkLyngsatForm
|
||||
success_message = "Привязка источников LyngSat завершена"
|
||||
error_message = "Ошибка при привязке источников"
|
||||
|
||||
def form_valid(self, form):
|
||||
from lyngsatapp.models import LyngSat
|
||||
|
||||
satellites = form.cleaned_data.get("satellites")
|
||||
frequency_tolerance = form.cleaned_data.get("frequency_tolerance", 0.5)
|
||||
|
||||
# If satellites not selected, process all
|
||||
if satellites:
|
||||
objitems = ObjItem.objects.filter(
|
||||
parameter_obj__id_satellite__in=satellites
|
||||
).select_related('parameter_obj', 'parameter_obj__polarization')
|
||||
else:
|
||||
objitems = ObjItem.objects.filter(
|
||||
parameter_obj__isnull=False
|
||||
).select_related('parameter_obj', 'parameter_obj__polarization')
|
||||
|
||||
linked_count = 0
|
||||
total_count = objitems.count()
|
||||
|
||||
for objitem in objitems:
|
||||
if not hasattr(objitem, 'parameter_obj') or not objitem.parameter_obj:
|
||||
continue
|
||||
|
||||
param = objitem.parameter_obj
|
||||
|
||||
# Round object frequency
|
||||
if param.frequency:
|
||||
rounded_freq = round(param.frequency, 0) # Round to integer
|
||||
|
||||
# Find matching LyngSat source
|
||||
# Compare by rounded frequency and polarization
|
||||
lyngsat_sources = LyngSat.objects.filter(
|
||||
id_satellite=param.id_satellite,
|
||||
polarization=param.polarization,
|
||||
frequency__gte=rounded_freq - frequency_tolerance,
|
||||
frequency__lte=rounded_freq + frequency_tolerance
|
||||
).order_by('frequency')
|
||||
|
||||
if lyngsat_sources.exists():
|
||||
# Take first matching source
|
||||
objitem.lyngsat_source = lyngsat_sources.first()
|
||||
objitem.save(update_fields=['lyngsat_source'])
|
||||
linked_count += 1
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
f"Привязано {linked_count} из {total_count} объектов к источникам LyngSat"
|
||||
)
|
||||
return redirect("mainapp:link_lyngsat")
|
||||
|
||||
def form_invalid(self, form):
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
|
||||
class FillLyngsatDataView(LoginRequiredMixin, FormMessageMixin, FormView):
|
||||
"""
|
||||
View for filling data from Lyngsat.
|
||||
|
||||
Allows selecting satellites and regions for parsing data from Lyngsat website.
|
||||
Starts asynchronous Celery task for processing.
|
||||
"""
|
||||
template_name = "mainapp/fill_lyngsat_data.html"
|
||||
form_class = FillLyngsatDataForm
|
||||
success_url = reverse_lazy("mainapp:lyngsat_task_status")
|
||||
error_message = "Форма заполнена некорректно"
|
||||
|
||||
def form_valid(self, form):
|
||||
satellites = form.cleaned_data["satellites"]
|
||||
regions = form.cleaned_data["regions"]
|
||||
use_cache = form.cleaned_data.get("use_cache", True)
|
||||
force_refresh = form.cleaned_data.get("force_refresh", False)
|
||||
|
||||
# Get satellite names
|
||||
target_sats = [sat.name for sat in satellites]
|
||||
|
||||
try:
|
||||
from lyngsatapp.tasks import fill_lyngsat_data_task
|
||||
|
||||
# Start asynchronous task with caching parameters
|
||||
task = fill_lyngsat_data_task.delay(
|
||||
target_sats,
|
||||
regions,
|
||||
force_refresh=force_refresh,
|
||||
use_cache=use_cache
|
||||
)
|
||||
|
||||
cache_status = "без кеша" if not use_cache else ("с обновлением кеша" if force_refresh else "с кешированием")
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
f"Задача запущена ({cache_status})! ID задачи: {task.id}. "
|
||||
"Вы будете перенаправлены на страницу отслеживания прогресса."
|
||||
)
|
||||
|
||||
# Redirect to task status page
|
||||
return redirect('mainapp:lyngsat_task_status', task_id=task.id)
|
||||
|
||||
except Exception as e:
|
||||
messages.error(self.request, f"Ошибка при запуске задачи: {str(e)}")
|
||||
return redirect("mainapp:fill_lyngsat_data")
|
||||
|
||||
|
||||
class LyngsatTaskStatusView(LoginRequiredMixin, View):
|
||||
"""View for tracking Lyngsat data filling task status."""
|
||||
|
||||
template_name = "mainapp/lyngsat_task_status.html"
|
||||
|
||||
def get(self, request, task_id=None):
|
||||
context = {
|
||||
'task_id': task_id
|
||||
}
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
|
||||
class ClearLyngsatCacheView(LoginRequiredMixin, View):
|
||||
"""View for clearing LyngSat cache."""
|
||||
|
||||
def post(self, request):
|
||||
from lyngsatapp.tasks import clear_cache_task
|
||||
|
||||
cache_type = request.POST.get('cache_type', 'all')
|
||||
|
||||
try:
|
||||
# Start cache clearing task
|
||||
task = clear_cache_task.delay(cache_type)
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
f"Задача очистки кеша ({cache_type}) запущена! ID задачи: {task.id}"
|
||||
)
|
||||
except Exception as e:
|
||||
messages.error(request, f"Ошибка при запуске задачи очистки кеша: {str(e)}")
|
||||
|
||||
return redirect(request.META.get('HTTP_REFERER', 'mainapp:home'))
|
||||
|
||||
def get(self, request):
|
||||
"""Cache management page."""
|
||||
return render(request, 'mainapp/clear_lyngsat_cache.html')
|
||||
139
dbapp/mainapp/views/map.py
Normal file
139
dbapp/mainapp/views/map.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
Map related views for displaying objects on maps.
|
||||
"""
|
||||
from collections import defaultdict
|
||||
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from ..clusters import get_clusters
|
||||
from ..mixins import RoleRequiredMixin
|
||||
from ..models import ObjItem
|
||||
|
||||
|
||||
@method_decorator(staff_member_required, name="dispatch")
|
||||
class ShowMapView(RoleRequiredMixin, View):
|
||||
"""View for displaying objects on map (admin interface)."""
|
||||
|
||||
required_roles = ["admin", "moderator"]
|
||||
|
||||
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).select_related(
|
||||
"parameter_obj",
|
||||
"parameter_obj__id_satellite",
|
||||
"parameter_obj__polarization",
|
||||
"parameter_obj__modulation",
|
||||
"parameter_obj__standard",
|
||||
"geo_obj",
|
||||
)
|
||||
for obj in locations:
|
||||
if (
|
||||
not hasattr(obj, "geo_obj")
|
||||
or not obj.geo_obj
|
||||
or not obj.geo_obj.coords
|
||||
):
|
||||
continue
|
||||
param = getattr(obj, 'parameter_obj', None)
|
||||
if not param:
|
||||
continue
|
||||
points.append(
|
||||
{
|
||||
"name": f"{obj.name}",
|
||||
"freq": f"{param.frequency} [{param.freq_range}] МГц",
|
||||
"point": (obj.geo_obj.coords.x, obj.geo_obj.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()
|
||||
]
|
||||
|
||||
context = {
|
||||
"groups": groups,
|
||||
}
|
||||
return render(request, "admin/map_custom.html", context)
|
||||
|
||||
|
||||
class ShowSelectedObjectsMapView(LoginRequiredMixin, View):
|
||||
"""View for displaying selected objects on map."""
|
||||
|
||||
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).select_related(
|
||||
"parameter_obj",
|
||||
"parameter_obj__id_satellite",
|
||||
"parameter_obj__polarization",
|
||||
"parameter_obj__modulation",
|
||||
"parameter_obj__standard",
|
||||
"geo_obj",
|
||||
)
|
||||
for obj in locations:
|
||||
if (
|
||||
not hasattr(obj, "geo_obj")
|
||||
or not obj.geo_obj
|
||||
or not obj.geo_obj.coords
|
||||
):
|
||||
continue
|
||||
param = getattr(obj, 'parameter_obj', None)
|
||||
if not param:
|
||||
continue
|
||||
points.append(
|
||||
{
|
||||
"name": f"{obj.name}",
|
||||
"freq": f"{param.frequency} [{param.freq_range}] МГц",
|
||||
"point": (obj.geo_obj.coords.x, obj.geo_obj.coords.y),
|
||||
}
|
||||
)
|
||||
else:
|
||||
return redirect("mainapp:objitem_list")
|
||||
|
||||
# Group points by object name
|
||||
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()
|
||||
]
|
||||
|
||||
context = {
|
||||
"groups": groups,
|
||||
}
|
||||
return render(request, "mainapp/objitem_map.html", context)
|
||||
|
||||
|
||||
class ClusterTestView(LoginRequiredMixin, View):
|
||||
"""Test view for clustering functionality."""
|
||||
|
||||
def get(self, request):
|
||||
objs = ObjItem.objects.filter(
|
||||
name__icontains="! Astra 4A 12654,040 [1,962] МГц H"
|
||||
)
|
||||
coords = []
|
||||
for obj in objs:
|
||||
if hasattr(obj, "geo_obj") and obj.geo_obj and obj.geo_obj.coords:
|
||||
coords.append(
|
||||
(obj.geo_obj.coords.coords[1], obj.geo_obj.coords.coords[0])
|
||||
)
|
||||
get_clusters(coords)
|
||||
|
||||
return JsonResponse({"success": "ок"})
|
||||
666
dbapp/mainapp/views/objitem.py
Normal file
666
dbapp/mainapp/views/objitem.py
Normal file
@@ -0,0 +1,666 @@
|
||||
"""
|
||||
ObjItem CRUD operations and related views.
|
||||
"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.paginator import Paginator
|
||||
from django.db import models
|
||||
from django.db.models import F
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse_lazy
|
||||
from django.views import View
|
||||
from django.views.generic import CreateView, DeleteView, UpdateView
|
||||
|
||||
from ..forms import GeoForm, ObjItemForm, ParameterForm
|
||||
from ..mixins import CoordinateProcessingMixin, FormMessageMixin, RoleRequiredMixin
|
||||
from ..models import Geo, Modulation, ObjItem, Polarization, Satellite
|
||||
from ..utils import parse_pagination_params
|
||||
|
||||
|
||||
class DeleteSelectedObjectsView(RoleRequiredMixin, View):
|
||||
"""View for deleting multiple selected objects."""
|
||||
|
||||
required_roles = ["admin", "moderator"]
|
||||
|
||||
def post(self, request):
|
||||
ids = request.POST.get("ids", "")
|
||||
if not ids:
|
||||
return JsonResponse({"error": "Нет ID для удаления"}, status=400)
|
||||
|
||||
try:
|
||||
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
|
||||
deleted_count, _ = ObjItem.objects.filter(id__in=id_list).delete()
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Объект успешно удалён",
|
||||
"deleted_count": deleted_count,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
return JsonResponse({"error": f"Ошибка при удалении: {str(e)}"}, status=500)
|
||||
|
||||
|
||||
class ObjItemListView(LoginRequiredMixin, View):
|
||||
"""View for displaying a list of ObjItems with filtering and pagination."""
|
||||
|
||||
def get(self, request):
|
||||
satellites = (
|
||||
Satellite.objects.filter(parameters__objitem__isnull=False)
|
||||
.distinct()
|
||||
.only("id", "name")
|
||||
.order_by("name")
|
||||
)
|
||||
|
||||
selected_sat_id = request.GET.get("satellite_id")
|
||||
page_number, items_per_page = parse_pagination_params(request)
|
||||
sort_param = request.GET.get("sort", "")
|
||||
|
||||
freq_min = request.GET.get("freq_min")
|
||||
freq_max = request.GET.get("freq_max")
|
||||
range_min = request.GET.get("range_min")
|
||||
range_max = request.GET.get("range_max")
|
||||
snr_min = request.GET.get("snr_min")
|
||||
snr_max = request.GET.get("snr_max")
|
||||
bod_min = request.GET.get("bod_min")
|
||||
bod_max = request.GET.get("bod_max")
|
||||
search_query = request.GET.get("search")
|
||||
selected_modulations = request.GET.getlist("modulation")
|
||||
selected_polarizations = request.GET.getlist("polarization")
|
||||
selected_satellites = request.GET.getlist("satellite_id")
|
||||
has_kupsat = request.GET.get("has_kupsat")
|
||||
has_valid = request.GET.get("has_valid")
|
||||
date_from = request.GET.get("date_from")
|
||||
date_to = request.GET.get("date_to")
|
||||
|
||||
objects = ObjItem.objects.none()
|
||||
|
||||
if selected_satellites or selected_sat_id:
|
||||
if selected_sat_id and not selected_satellites:
|
||||
try:
|
||||
selected_sat_id_single = int(selected_sat_id)
|
||||
selected_satellites = [selected_sat_id_single]
|
||||
except ValueError:
|
||||
selected_satellites = []
|
||||
|
||||
if selected_satellites:
|
||||
objects = (
|
||||
ObjItem.objects.select_related(
|
||||
"geo_obj",
|
||||
"source",
|
||||
"updated_by__user",
|
||||
"created_by__user",
|
||||
"lyngsat_source",
|
||||
"parameter_obj",
|
||||
"parameter_obj__id_satellite",
|
||||
"parameter_obj__polarization",
|
||||
"parameter_obj__modulation",
|
||||
"parameter_obj__standard",
|
||||
)
|
||||
.prefetch_related(
|
||||
"parameter_obj__sigma_parameter",
|
||||
"parameter_obj__sigma_parameter__polarization",
|
||||
)
|
||||
.filter(parameter_obj__id_satellite_id__in=selected_satellites)
|
||||
)
|
||||
else:
|
||||
objects = ObjItem.objects.select_related(
|
||||
"geo_obj",
|
||||
"source",
|
||||
"updated_by__user",
|
||||
"created_by__user",
|
||||
"lyngsat_source",
|
||||
"parameter_obj",
|
||||
"parameter_obj__id_satellite",
|
||||
"parameter_obj__polarization",
|
||||
"parameter_obj__modulation",
|
||||
"parameter_obj__standard",
|
||||
).prefetch_related(
|
||||
"parameter_obj__sigma_parameter",
|
||||
"parameter_obj__sigma_parameter__polarization",
|
||||
)
|
||||
|
||||
if freq_min is not None and freq_min.strip() != "":
|
||||
try:
|
||||
freq_min_val = float(freq_min)
|
||||
objects = objects.filter(
|
||||
parameter_obj__frequency__gte=freq_min_val
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
if freq_max is not None and freq_max.strip() != "":
|
||||
try:
|
||||
freq_max_val = float(freq_max)
|
||||
objects = objects.filter(
|
||||
parameter_obj__frequency__lte=freq_max_val
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if range_min is not None and range_min.strip() != "":
|
||||
try:
|
||||
range_min_val = float(range_min)
|
||||
objects = objects.filter(
|
||||
parameter_obj__freq_range__gte=range_min_val
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
if range_max is not None and range_max.strip() != "":
|
||||
try:
|
||||
range_max_val = float(range_max)
|
||||
objects = objects.filter(
|
||||
parameter_obj__freq_range__lte=range_max_val
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if snr_min is not None and snr_min.strip() != "":
|
||||
try:
|
||||
snr_min_val = float(snr_min)
|
||||
objects = objects.filter(parameter_obj__snr__gte=snr_min_val)
|
||||
except ValueError:
|
||||
pass
|
||||
if snr_max is not None and snr_max.strip() != "":
|
||||
try:
|
||||
snr_max_val = float(snr_max)
|
||||
objects = objects.filter(parameter_obj__snr__lte=snr_max_val)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if bod_min is not None and bod_min.strip() != "":
|
||||
try:
|
||||
bod_min_val = float(bod_min)
|
||||
objects = objects.filter(
|
||||
parameter_obj__bod_velocity__gte=bod_min_val
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
if bod_max is not None and bod_max.strip() != "":
|
||||
try:
|
||||
bod_max_val = float(bod_max)
|
||||
objects = objects.filter(
|
||||
parameter_obj__bod_velocity__lte=bod_max_val
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if selected_modulations:
|
||||
objects = objects.filter(
|
||||
parameter_obj__modulation__id__in=selected_modulations
|
||||
)
|
||||
|
||||
if selected_polarizations:
|
||||
objects = objects.filter(
|
||||
parameter_obj__polarization__id__in=selected_polarizations
|
||||
)
|
||||
|
||||
if has_kupsat == "1":
|
||||
objects = objects.filter(source__coords_kupsat__isnull=False)
|
||||
elif has_kupsat == "0":
|
||||
objects = objects.filter(source__coords_kupsat__isnull=True)
|
||||
|
||||
if has_valid == "1":
|
||||
objects = objects.filter(source__coords_valid__isnull=False)
|
||||
elif has_valid == "0":
|
||||
objects = objects.filter(source__coords_valid__isnull=True)
|
||||
|
||||
# Date filter for geo_obj timestamp
|
||||
if date_from and date_from.strip():
|
||||
try:
|
||||
from datetime import datetime
|
||||
date_from_obj = datetime.strptime(date_from, "%Y-%m-%d")
|
||||
objects = objects.filter(geo_obj__timestamp__gte=date_from_obj)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if date_to and date_to.strip():
|
||||
try:
|
||||
from datetime import datetime, timedelta
|
||||
date_to_obj = datetime.strptime(date_to, "%Y-%m-%d")
|
||||
# Add one day to include the entire end date
|
||||
date_to_obj = date_to_obj + timedelta(days=1)
|
||||
objects = objects.filter(geo_obj__timestamp__lt=date_to_obj)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Filter by source type (lyngsat_source)
|
||||
has_source_type = request.GET.get("has_source_type")
|
||||
if has_source_type == "1":
|
||||
objects = objects.filter(lyngsat_source__isnull=False)
|
||||
elif has_source_type == "0":
|
||||
objects = objects.filter(lyngsat_source__isnull=True)
|
||||
|
||||
# Filter by sigma (sigma parameters)
|
||||
has_sigma = request.GET.get("has_sigma")
|
||||
if has_sigma == "1":
|
||||
objects = objects.filter(parameter_obj__sigma_parameter__isnull=False)
|
||||
elif has_sigma == "0":
|
||||
objects = objects.filter(parameter_obj__sigma_parameter__isnull=True)
|
||||
|
||||
if search_query:
|
||||
search_query = search_query.strip()
|
||||
if search_query:
|
||||
objects = objects.filter(
|
||||
models.Q(name__icontains=search_query)
|
||||
| models.Q(geo_obj__location__icontains=search_query)
|
||||
)
|
||||
else:
|
||||
selected_sat_id = None
|
||||
|
||||
objects = objects.annotate(
|
||||
first_param_freq=F("parameter_obj__frequency"),
|
||||
first_param_range=F("parameter_obj__freq_range"),
|
||||
first_param_snr=F("parameter_obj__snr"),
|
||||
first_param_bod=F("parameter_obj__bod_velocity"),
|
||||
first_param_sat_name=F("parameter_obj__id_satellite__name"),
|
||||
first_param_pol_name=F("parameter_obj__polarization__name"),
|
||||
first_param_mod_name=F("parameter_obj__modulation__name"),
|
||||
)
|
||||
|
||||
valid_sort_fields = {
|
||||
"name": "name",
|
||||
"-name": "-name",
|
||||
"updated_at": "updated_at",
|
||||
"-updated_at": "-updated_at",
|
||||
"created_at": "created_at",
|
||||
"-created_at": "-created_at",
|
||||
"updated_by": "updated_by__user__username",
|
||||
"-updated_by": "-updated_by__user__username",
|
||||
"created_by": "created_by__user__username",
|
||||
"-created_by": "-created_by__user__username",
|
||||
"geo_timestamp": "geo_obj__timestamp",
|
||||
"-geo_timestamp": "-geo_obj__timestamp",
|
||||
"frequency": "first_param_freq",
|
||||
"-frequency": "-first_param_freq",
|
||||
"freq_range": "first_param_range",
|
||||
"-freq_range": "-first_param_range",
|
||||
"snr": "first_param_snr",
|
||||
"-snr": "-first_param_snr",
|
||||
"bod_velocity": "first_param_bod",
|
||||
"-bod_velocity": "-first_param_bod",
|
||||
"satellite": "first_param_sat_name",
|
||||
"-satellite": "-first_param_sat_name",
|
||||
"polarization": "first_param_pol_name",
|
||||
"-polarization": "-first_param_pol_name",
|
||||
"modulation": "first_param_mod_name",
|
||||
"-modulation": "-first_param_mod_name",
|
||||
}
|
||||
|
||||
if sort_param in valid_sort_fields:
|
||||
objects = objects.order_by(valid_sort_fields[sort_param])
|
||||
|
||||
paginator = Paginator(objects, items_per_page)
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
processed_objects = []
|
||||
for obj in page_obj:
|
||||
param = getattr(obj, 'parameter_obj', None)
|
||||
|
||||
geo_coords = "-"
|
||||
geo_timestamp = "-"
|
||||
geo_location = "-"
|
||||
kupsat_coords = "-"
|
||||
valid_coords = "-"
|
||||
distance_geo_kup = "-"
|
||||
distance_geo_valid = "-"
|
||||
distance_kup_valid = "-"
|
||||
|
||||
if hasattr(obj, "geo_obj") and obj.geo_obj:
|
||||
geo_timestamp = obj.geo_obj.timestamp
|
||||
geo_location = obj.geo_obj.location
|
||||
|
||||
if obj.geo_obj.coords:
|
||||
longitude = obj.geo_obj.coords.coords[0]
|
||||
latitude = obj.geo_obj.coords.coords[1]
|
||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
||||
geo_coords = f"{lat} {lon}"
|
||||
|
||||
satellite_name = "-"
|
||||
frequency = "-"
|
||||
freq_range = "-"
|
||||
polarization_name = "-"
|
||||
bod_velocity = "-"
|
||||
modulation_name = "-"
|
||||
snr = "-"
|
||||
standard_name = "-"
|
||||
comment = "-"
|
||||
is_average = "-"
|
||||
|
||||
if param:
|
||||
if hasattr(param, "id_satellite") and param.id_satellite:
|
||||
satellite_name = (
|
||||
param.id_satellite.name
|
||||
if hasattr(param.id_satellite, "name")
|
||||
else "-"
|
||||
)
|
||||
|
||||
frequency = (
|
||||
f"{param.frequency:.3f}" if param.frequency is not None else "-"
|
||||
)
|
||||
freq_range = (
|
||||
f"{param.freq_range:.3f}" if param.freq_range is not None else "-"
|
||||
)
|
||||
bod_velocity = (
|
||||
f"{param.bod_velocity:.0f}"
|
||||
if param.bod_velocity is not None
|
||||
else "-"
|
||||
)
|
||||
snr = f"{param.snr:.0f}" if param.snr is not None else "-"
|
||||
|
||||
if hasattr(param, "polarization") and param.polarization:
|
||||
polarization_name = (
|
||||
param.polarization.name
|
||||
if hasattr(param.polarization, "name")
|
||||
else "-"
|
||||
)
|
||||
|
||||
if hasattr(param, "modulation") and param.modulation:
|
||||
modulation_name = (
|
||||
param.modulation.name
|
||||
if hasattr(param.modulation, "name")
|
||||
else "-"
|
||||
)
|
||||
|
||||
if hasattr(param, "standard") and param.standard:
|
||||
standard_name = (
|
||||
param.standard.name
|
||||
if hasattr(param.standard, "name")
|
||||
else "-"
|
||||
)
|
||||
|
||||
if hasattr(obj, "geo_obj") and obj.geo_obj:
|
||||
comment = obj.geo_obj.comment or "-"
|
||||
is_average = "Да" if obj.geo_obj.is_average else "Нет" if obj.geo_obj.is_average is not None else "-"
|
||||
|
||||
source_type = "ТВ" if obj.lyngsat_source else "-"
|
||||
|
||||
has_sigma = False
|
||||
sigma_info = "-"
|
||||
if param:
|
||||
sigma_count = param.sigma_parameter.count()
|
||||
if sigma_count > 0:
|
||||
has_sigma = True
|
||||
first_sigma = param.sigma_parameter.first()
|
||||
if first_sigma:
|
||||
sigma_freq = f"{first_sigma.transfer_frequency:.3f}" if first_sigma.transfer_frequency else "-"
|
||||
sigma_range = f"{first_sigma.freq_range:.3f}" if first_sigma.freq_range else "-"
|
||||
sigma_pol = first_sigma.polarization.name if first_sigma.polarization else "-"
|
||||
sigma_pol_short = sigma_pol[0] if sigma_pol and sigma_pol != "-" else "-"
|
||||
sigma_info = f"{sigma_freq}/{sigma_range}/{sigma_pol_short}"
|
||||
|
||||
processed_objects.append(
|
||||
{
|
||||
"id": obj.id,
|
||||
"name": obj.name or "-",
|
||||
"satellite_name": satellite_name,
|
||||
"frequency": frequency,
|
||||
"freq_range": freq_range,
|
||||
"polarization": polarization_name,
|
||||
"bod_velocity": bod_velocity,
|
||||
"modulation": modulation_name,
|
||||
"snr": snr,
|
||||
"geo_timestamp": geo_timestamp,
|
||||
"geo_location": geo_location,
|
||||
"geo_coords": geo_coords,
|
||||
"kupsat_coords": kupsat_coords,
|
||||
"valid_coords": valid_coords,
|
||||
"distance_geo_kup": distance_geo_kup,
|
||||
"distance_geo_valid": distance_geo_valid,
|
||||
"distance_kup_valid": distance_kup_valid,
|
||||
"updated_by": obj.updated_by if obj.updated_by else "-",
|
||||
"comment": comment,
|
||||
"is_average": is_average,
|
||||
"source_type": source_type,
|
||||
"standard": standard_name,
|
||||
"has_sigma": has_sigma,
|
||||
"sigma_info": sigma_info,
|
||||
"obj": obj,
|
||||
}
|
||||
)
|
||||
|
||||
modulations = Modulation.objects.all()
|
||||
polarizations = Polarization.objects.all()
|
||||
|
||||
# Get the new filter values
|
||||
has_source_type = request.GET.get("has_source_type")
|
||||
has_sigma = request.GET.get("has_sigma")
|
||||
|
||||
context = {
|
||||
"satellites": satellites,
|
||||
"selected_satellite_id": selected_sat_id,
|
||||
"page_obj": page_obj,
|
||||
"processed_objects": processed_objects,
|
||||
"items_per_page": items_per_page,
|
||||
"available_items_per_page": [50, 100, 500, 1000],
|
||||
"freq_min": freq_min,
|
||||
"freq_max": freq_max,
|
||||
"range_min": range_min,
|
||||
"range_max": range_max,
|
||||
"snr_min": snr_min,
|
||||
"snr_max": snr_max,
|
||||
"bod_min": bod_min,
|
||||
"bod_max": bod_max,
|
||||
"search_query": search_query,
|
||||
"selected_modulations": [
|
||||
int(x) for x in selected_modulations if x.isdigit()
|
||||
],
|
||||
"selected_polarizations": [
|
||||
int(x) for x in selected_polarizations if x.isdigit()
|
||||
],
|
||||
"selected_satellites": [int(x) for x in selected_satellites if x.isdigit()],
|
||||
"has_kupsat": has_kupsat,
|
||||
"has_valid": has_valid,
|
||||
"date_from": date_from,
|
||||
"date_to": date_to,
|
||||
"has_source_type": has_source_type,
|
||||
"has_sigma": has_sigma,
|
||||
"modulations": modulations,
|
||||
"polarizations": polarizations,
|
||||
"full_width_page": True,
|
||||
"sort": sort_param,
|
||||
}
|
||||
|
||||
return render(request, "mainapp/objitem_list.html", context)
|
||||
|
||||
|
||||
class ObjItemFormView(
|
||||
RoleRequiredMixin, CoordinateProcessingMixin, FormMessageMixin, UpdateView
|
||||
):
|
||||
"""
|
||||
Base class for creating and editing ObjItem.
|
||||
|
||||
Contains common logic for form processing, coordinates, and parameters.
|
||||
"""
|
||||
|
||||
model = ObjItem
|
||||
form_class = ObjItemForm
|
||||
template_name = "mainapp/objitem_form.html"
|
||||
success_url = reverse_lazy("mainapp:home")
|
||||
required_roles = ["admin", "moderator"]
|
||||
|
||||
def get_success_url(self):
|
||||
"""Returns URL with saved filter parameters."""
|
||||
if self.request.GET:
|
||||
from urllib.parse import urlencode
|
||||
query_string = urlencode(self.request.GET)
|
||||
return reverse_lazy("mainapp:objitem_list") + '?' + query_string
|
||||
return reverse_lazy("mainapp:objitem_list")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["LEAFLET_CONFIG"] = {
|
||||
"DEFAULT_CENTER": (55.75, 37.62),
|
||||
"DEFAULT_ZOOM": 5,
|
||||
}
|
||||
|
||||
# Save return parameters for "Back" button
|
||||
context["return_params"] = self.request.GET.get('return_params', '')
|
||||
|
||||
# Work with single parameter form instead of formset
|
||||
if self.object and hasattr(self.object, "parameter_obj") and self.object.parameter_obj:
|
||||
context["parameter_form"] = ParameterForm(
|
||||
instance=self.object.parameter_obj, prefix="parameter"
|
||||
)
|
||||
else:
|
||||
context["parameter_form"] = ParameterForm(prefix="parameter")
|
||||
|
||||
if self.object and hasattr(self.object, "geo_obj") and self.object.geo_obj:
|
||||
context["geo_form"] = GeoForm(
|
||||
instance=self.object.geo_obj, prefix="geo"
|
||||
)
|
||||
else:
|
||||
context["geo_form"] = GeoForm(prefix="geo")
|
||||
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
# Get parameter form
|
||||
if self.object and hasattr(self.object, "parameter_obj") and self.object.parameter_obj:
|
||||
parameter_form = ParameterForm(
|
||||
self.request.POST,
|
||||
instance=self.object.parameter_obj,
|
||||
prefix="parameter"
|
||||
)
|
||||
else:
|
||||
parameter_form = ParameterForm(self.request.POST, prefix="parameter")
|
||||
|
||||
if self.object and hasattr(self.object, "geo_obj") and self.object.geo_obj:
|
||||
geo_form = GeoForm(self.request.POST, instance=self.object.geo_obj, prefix="geo")
|
||||
else:
|
||||
geo_form = GeoForm(self.request.POST, prefix="geo")
|
||||
|
||||
# Save main object
|
||||
self.object = form.save(commit=False)
|
||||
self.set_user_fields()
|
||||
self.object.save()
|
||||
|
||||
# Save related parameter
|
||||
if parameter_form.is_valid():
|
||||
self.save_parameter(parameter_form)
|
||||
else:
|
||||
context = self.get_context_data()
|
||||
context.update({
|
||||
'form': form,
|
||||
'parameter_form': parameter_form,
|
||||
'geo_form': geo_form,
|
||||
})
|
||||
return self.render_to_response(context)
|
||||
|
||||
# Save geo data
|
||||
if geo_form.is_valid():
|
||||
self.save_geo_data(geo_form)
|
||||
else:
|
||||
context = self.get_context_data()
|
||||
context.update({
|
||||
'form': form,
|
||||
'parameter_form': parameter_form,
|
||||
'geo_form': geo_form,
|
||||
})
|
||||
return self.render_to_response(context)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def set_user_fields(self):
|
||||
"""Sets user fields for the object."""
|
||||
raise NotImplementedError("Subclasses must implement set_user_fields()")
|
||||
|
||||
def save_parameter(self, parameter_form):
|
||||
"""Saves object parameter through OneToOne relationship."""
|
||||
if parameter_form.is_valid():
|
||||
instance = parameter_form.save(commit=False)
|
||||
instance.objitem = self.object
|
||||
instance.save()
|
||||
|
||||
def save_geo_data(self, geo_form):
|
||||
"""Saves object geo data."""
|
||||
geo_instance = self.get_or_create_geo_instance()
|
||||
|
||||
# Update fields from geo_form
|
||||
if geo_form.is_valid():
|
||||
geo_instance.location = geo_form.cleaned_data["location"]
|
||||
geo_instance.comment = geo_form.cleaned_data["comment"]
|
||||
geo_instance.is_average = geo_form.cleaned_data["is_average"]
|
||||
|
||||
# Process date/time
|
||||
self.process_timestamp(geo_instance)
|
||||
|
||||
geo_instance.save()
|
||||
|
||||
def get_or_create_geo_instance(self):
|
||||
"""Gets or creates Geo instance."""
|
||||
if hasattr(self.object, "geo_obj") and self.object.geo_obj:
|
||||
return self.object.geo_obj
|
||||
return Geo(objitem=self.object)
|
||||
|
||||
|
||||
class ObjItemUpdateView(ObjItemFormView):
|
||||
"""View for editing ObjItem."""
|
||||
|
||||
success_message = "Объект успешно сохранён!"
|
||||
|
||||
def set_user_fields(self):
|
||||
self.object.updated_by = self.request.user.customuser
|
||||
|
||||
|
||||
class ObjItemCreateView(ObjItemFormView, CreateView):
|
||||
"""View for creating ObjItem."""
|
||||
|
||||
success_message = "Объект успешно создан!"
|
||||
|
||||
def set_user_fields(self):
|
||||
self.object.created_by = self.request.user.customuser
|
||||
self.object.updated_by = self.request.user.customuser
|
||||
|
||||
|
||||
class ObjItemDeleteView(RoleRequiredMixin, FormMessageMixin, DeleteView):
|
||||
"""View for deleting ObjItem."""
|
||||
|
||||
model = ObjItem
|
||||
template_name = "mainapp/objitem_confirm_delete.html"
|
||||
success_url = reverse_lazy("mainapp:objitem_list")
|
||||
success_message = "Объект успешно удалён!"
|
||||
required_roles = ["admin", "moderator"]
|
||||
|
||||
def get_success_url(self):
|
||||
"""Returns URL with saved filter parameters."""
|
||||
if self.request.GET:
|
||||
from urllib.parse import urlencode
|
||||
query_string = urlencode(self.request.GET)
|
||||
return reverse_lazy("mainapp:objitem_list") + '?' + query_string
|
||||
return reverse_lazy("mainapp:objitem_list")
|
||||
|
||||
|
||||
class ObjItemDetailView(LoginRequiredMixin, View):
|
||||
"""
|
||||
View for displaying ObjItem details in read-only mode.
|
||||
|
||||
Available to all authenticated users, displays data in read-only mode.
|
||||
"""
|
||||
def get(self, request, pk):
|
||||
obj = ObjItem.objects.filter(pk=pk).select_related(
|
||||
'geo_obj',
|
||||
'updated_by__user',
|
||||
'created_by__user',
|
||||
'parameter_obj',
|
||||
'parameter_obj__id_satellite',
|
||||
'parameter_obj__polarization',
|
||||
'parameter_obj__modulation',
|
||||
'parameter_obj__standard',
|
||||
).first()
|
||||
|
||||
if not obj:
|
||||
from django.http import Http404
|
||||
raise Http404("Объект не найден")
|
||||
|
||||
# Save return parameters for "Back" button
|
||||
return_params = request.GET.get('return_params', '')
|
||||
|
||||
context = {
|
||||
'object': obj,
|
||||
'return_params': return_params
|
||||
}
|
||||
|
||||
return render(request, "mainapp/objitem_detail.html", context)
|
||||
190
dbapp/mainapp/views/source.py
Normal file
190
dbapp/mainapp/views/source.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
Source related views.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import render
|
||||
from django.views import View
|
||||
|
||||
from ..models import Source
|
||||
from ..utils import parse_pagination_params
|
||||
|
||||
|
||||
class SourceListView(LoginRequiredMixin, View):
|
||||
"""
|
||||
View for displaying a list of sources (Source).
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
# Get pagination parameters
|
||||
page_number, items_per_page = parse_pagination_params(request)
|
||||
|
||||
# Get sorting parameters
|
||||
sort_param = request.GET.get("sort", "-created_at")
|
||||
|
||||
# Get filter parameters
|
||||
search_query = request.GET.get("search", "").strip()
|
||||
has_coords_average = request.GET.get("has_coords_average")
|
||||
has_coords_kupsat = request.GET.get("has_coords_kupsat")
|
||||
has_coords_valid = request.GET.get("has_coords_valid")
|
||||
has_coords_reference = request.GET.get("has_coords_reference")
|
||||
objitem_count_min = request.GET.get("objitem_count_min", "").strip()
|
||||
objitem_count_max = request.GET.get("objitem_count_max", "").strip()
|
||||
date_from = request.GET.get("date_from", "").strip()
|
||||
date_to = request.GET.get("date_to", "").strip()
|
||||
|
||||
# Get all Source objects with query optimization
|
||||
sources = Source.objects.select_related(
|
||||
'created_by__user',
|
||||
'updated_by__user'
|
||||
).prefetch_related(
|
||||
'source_objitems',
|
||||
'source_objitems__parameter_obj',
|
||||
'source_objitems__geo_obj'
|
||||
).annotate(
|
||||
objitem_count=Count('source_objitems')
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
# Filter by coords_average presence
|
||||
if has_coords_average == "1":
|
||||
sources = sources.filter(coords_average__isnull=False)
|
||||
elif has_coords_average == "0":
|
||||
sources = sources.filter(coords_average__isnull=True)
|
||||
|
||||
# Filter by coords_kupsat presence
|
||||
if has_coords_kupsat == "1":
|
||||
sources = sources.filter(coords_kupsat__isnull=False)
|
||||
elif has_coords_kupsat == "0":
|
||||
sources = sources.filter(coords_kupsat__isnull=True)
|
||||
|
||||
# Filter by coords_valid presence
|
||||
if has_coords_valid == "1":
|
||||
sources = sources.filter(coords_valid__isnull=False)
|
||||
elif has_coords_valid == "0":
|
||||
sources = sources.filter(coords_valid__isnull=True)
|
||||
|
||||
# Filter by coords_reference presence
|
||||
if has_coords_reference == "1":
|
||||
sources = sources.filter(coords_reference__isnull=False)
|
||||
elif has_coords_reference == "0":
|
||||
sources = sources.filter(coords_reference__isnull=True)
|
||||
|
||||
# Filter by ObjItem count
|
||||
if objitem_count_min:
|
||||
try:
|
||||
min_count = int(objitem_count_min)
|
||||
sources = sources.filter(objitem_count__gte=min_count)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if objitem_count_max:
|
||||
try:
|
||||
max_count = int(objitem_count_max)
|
||||
sources = sources.filter(objitem_count__lte=max_count)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Filter by creation date range
|
||||
if date_from:
|
||||
try:
|
||||
date_from_obj = datetime.strptime(date_from, "%Y-%m-%d")
|
||||
sources = sources.filter(created_at__gte=date_from_obj)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if date_to:
|
||||
try:
|
||||
from datetime import timedelta
|
||||
date_to_obj = datetime.strptime(date_to, "%Y-%m-%d")
|
||||
# Add one day to include entire end date
|
||||
date_to_obj = date_to_obj + timedelta(days=1)
|
||||
sources = sources.filter(created_at__lt=date_to_obj)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Search by ID
|
||||
if search_query:
|
||||
try:
|
||||
search_id = int(search_query)
|
||||
sources = sources.filter(id=search_id)
|
||||
except ValueError:
|
||||
# If not a number, ignore
|
||||
pass
|
||||
|
||||
# Apply sorting
|
||||
valid_sort_fields = {
|
||||
"id": "id",
|
||||
"-id": "-id",
|
||||
"created_at": "created_at",
|
||||
"-created_at": "-created_at",
|
||||
"updated_at": "updated_at",
|
||||
"-updated_at": "-updated_at",
|
||||
"objitem_count": "objitem_count",
|
||||
"-objitem_count": "-objitem_count",
|
||||
}
|
||||
|
||||
if sort_param in valid_sort_fields:
|
||||
sources = sources.order_by(valid_sort_fields[sort_param])
|
||||
|
||||
# Create paginator
|
||||
paginator = Paginator(sources, items_per_page)
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
# Prepare data for display
|
||||
processed_sources = []
|
||||
for source in page_obj:
|
||||
# Format coordinates
|
||||
def format_coords(point):
|
||||
if point:
|
||||
longitude = point.coords[0]
|
||||
latitude = point.coords[1]
|
||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
||||
return f"{lat} {lon}"
|
||||
return "-"
|
||||
|
||||
coords_average_str = format_coords(source.coords_average)
|
||||
coords_kupsat_str = format_coords(source.coords_kupsat)
|
||||
coords_valid_str = format_coords(source.coords_valid)
|
||||
coords_reference_str = format_coords(source.coords_reference)
|
||||
|
||||
# Get count of related ObjItems
|
||||
objitem_count = source.objitem_count
|
||||
|
||||
processed_sources.append({
|
||||
'id': source.id,
|
||||
'coords_average': coords_average_str,
|
||||
'coords_kupsat': coords_kupsat_str,
|
||||
'coords_valid': coords_valid_str,
|
||||
'coords_reference': coords_reference_str,
|
||||
'objitem_count': objitem_count,
|
||||
'created_at': source.created_at,
|
||||
'updated_at': source.updated_at,
|
||||
'created_by': source.created_by,
|
||||
'updated_by': source.updated_by,
|
||||
})
|
||||
|
||||
# Prepare context for template
|
||||
context = {
|
||||
'page_obj': page_obj,
|
||||
'processed_sources': processed_sources,
|
||||
'items_per_page': items_per_page,
|
||||
'available_items_per_page': [50, 100, 500, 1000],
|
||||
'sort': sort_param,
|
||||
'search_query': search_query,
|
||||
'has_coords_average': has_coords_average,
|
||||
'has_coords_kupsat': has_coords_kupsat,
|
||||
'has_coords_valid': has_coords_valid,
|
||||
'has_coords_reference': has_coords_reference,
|
||||
'objitem_count_min': objitem_count_min,
|
||||
'objitem_count_max': objitem_count_max,
|
||||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
'full_width_page': True,
|
||||
}
|
||||
|
||||
return render(request, "mainapp/source_list.html", context)
|
||||
Reference in New Issue
Block a user