Переделал модель Parameter и связь с ObjItem

This commit is contained in:
2025-11-10 22:32:26 +03:00
parent b24ef940ce
commit 1b345a3fd9
9 changed files with 535 additions and 447 deletions

View File

@@ -218,11 +218,11 @@ def export_objects_to_csv(modeladmin, request, queryset):
queryset = queryset.select_related(
'geo_obj',
'created_by__user',
'updated_by__user'
).prefetch_related(
'parameters_obj__id_satellite',
'parameters_obj__polarization',
'parameters_obj__modulation'
'updated_by__user',
'parameter_obj',
'parameter_obj__id_satellite',
'parameter_obj__polarization',
'parameter_obj__modulation'
)
response = HttpResponse(content_type='text/csv; charset=utf-8')
@@ -248,7 +248,7 @@ def export_objects_to_csv(modeladmin, request, queryset):
])
for obj in queryset:
param = next(iter(obj.parameters_obj.all()), None)
param = getattr(obj, 'parameter_obj', None)
geo = obj.geo_obj
# Форматирование координат
@@ -284,12 +284,25 @@ def export_objects_to_csv(modeladmin, request, queryset):
# Inline Admin Classes
# ============================================================================
class ParameterObjItemInline(admin.StackedInline):
model = ObjItem.parameters_obj.through
class ParameterInline(admin.StackedInline):
"""Inline для редактирования параметра объекта."""
model = Parameter
extra = 0
max_num = 1
can_delete = True
verbose_name = "ВЧ загрузка"
verbose_name_plural = "ВЧ загрузки"
verbose_name_plural = "ВЧ загрузка"
fields = (
'id_satellite',
'frequency',
'freq_range',
'polarization',
'modulation',
'bod_velocity',
'snr',
'standard'
)
autocomplete_fields = ('id_satellite', 'polarization', 'modulation', 'standard')
# ============================================================================
@@ -370,13 +383,15 @@ class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"bod_velocity",
"snr",
"standard",
"related_objitem",
"sigma_parameter"
)
list_display_links = ("frequency", "id_satellite")
list_select_related = ("polarization", "modulation", "standard", "id_satellite")
list_select_related = ("polarization", "modulation", "standard", "id_satellite", "objitem")
list_filter = (
HasSigmaParameterFilter,
("objitem", MultiSelectRelatedDropdownFilter),
("id_satellite", MultiSelectRelatedDropdownFilter),
("polarization__name", MultiSelectDropdownFilter),
("modulation", MultiSelectRelatedDropdownFilter),
@@ -395,12 +410,21 @@ class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"modulation__name",
"polarization__name",
"standard__name",
"objitem__name",
)
ordering = ("-frequency",)
autocomplete_fields = ("objitems",)
autocomplete_fields = ("objitem",)
inlines = [SigmaParameterInline]
def related_objitem(self, obj):
"""Отображает связанный ObjItem."""
if hasattr(obj, 'objitem') and obj.objitem:
return obj.objitem.name
return "-"
related_objitem.short_description = "Объект"
related_objitem.admin_order_field = "objitem__name"
def sigma_parameter(self, obj):
"""Отображает связанный параметр Sigma."""
sigma_obj = obj.sigma_parameter.all()
@@ -636,16 +660,25 @@ class ObjItemAdmin(BaseAdmin):
"updated_at",
)
list_display_links = ("name",)
list_select_related = ("geo_obj", "created_by__user", "updated_by__user")
list_select_related = (
"geo_obj",
"created_by__user",
"updated_by__user",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard"
)
list_filter = (
UniqueToggleFilter,
("parameters_obj__id_satellite", MultiSelectRelatedDropdownFilter),
("parameters_obj__frequency", NumericRangeFilterBuilder()),
("parameters_obj__freq_range", NumericRangeFilterBuilder()),
("parameters_obj__snr", NumericRangeFilterBuilder()),
("parameters_obj__modulation", MultiSelectRelatedDropdownFilter),
("parameters_obj__polarization", MultiSelectRelatedDropdownFilter),
("parameter_obj__id_satellite", MultiSelectRelatedDropdownFilter),
("parameter_obj__frequency", NumericRangeFilterBuilder()),
("parameter_obj__freq_range", NumericRangeFilterBuilder()),
("parameter_obj__snr", NumericRangeFilterBuilder()),
("parameter_obj__modulation", MultiSelectRelatedDropdownFilter),
("parameter_obj__polarization", MultiSelectRelatedDropdownFilter),
GeoKupDistanceFilter,
GeoValidDistanceFilter,
("created_at", DateRangeQuickSelectListFilterBuilder()),
@@ -655,12 +688,12 @@ class ObjItemAdmin(BaseAdmin):
search_fields = (
"name",
"geo_obj__location",
"parameters_obj__frequency",
"parameters_obj__id_satellite__name",
"parameter_obj__frequency",
"parameter_obj__id_satellite__name",
)
ordering = ("-updated_at",)
inlines = [ParameterObjItemInline, GeoInline]
inlines = [GeoInline, ParameterInline]
actions = [show_selected_on_map, export_objects_to_csv]
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
@@ -676,7 +709,7 @@ class ObjItemAdmin(BaseAdmin):
def get_queryset(self, request):
"""
Оптимизированный queryset с использованием select_related и prefetch_related.
Оптимизированный queryset с использованием select_related.
Загружает связанные объекты одним запросом для улучшения производительности.
"""
@@ -684,31 +717,30 @@ class ObjItemAdmin(BaseAdmin):
return qs.select_related(
"geo_obj",
"created_by__user",
"updated_by__user"
).prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard"
"updated_by__user",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard"
)
def sat_name(self, obj):
"""Отображает название спутника из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param and param.id_satellite:
return param.id_satellite.name
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
if obj.parameter_obj.id_satellite:
return obj.parameter_obj.id_satellite.name
return "-"
sat_name.short_description = "Спутник"
sat_name.admin_order_field = "parameters_obj__id_satellite__name"
sat_name.admin_order_field = "parameter_obj__id_satellite__name"
def freq(self, obj):
"""Отображает частоту из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.frequency
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
return obj.parameter_obj.frequency
return "-"
freq.short_description = "Частота, МГц"
freq.admin_order_field = "parameters_obj__frequency"
freq.admin_order_field = "parameter_obj__frequency"
def distance_geo_kup(self, obj):
"""Отображает расстояние между геолокацией и Кубсатом."""
@@ -736,42 +768,39 @@ class ObjItemAdmin(BaseAdmin):
def pol(self, obj):
"""Отображает поляризацию из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param and param.polarization:
return param.polarization.name
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
if obj.parameter_obj.polarization:
return obj.parameter_obj.polarization.name
return "-"
pol.short_description = "Поляризация"
def freq_range(self, obj):
"""Отображает полосу частот из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.freq_range
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
return obj.parameter_obj.freq_range
return "-"
freq_range.short_description = "Полоса, МГц"
freq_range.admin_order_field = "parameters_obj__freq_range"
freq_range.admin_order_field = "parameter_obj__freq_range"
def bod_velocity(self, obj):
"""Отображает символьную скорость из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.bod_velocity
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
return obj.parameter_obj.bod_velocity
return "-"
bod_velocity.short_description = "Сим. v, БОД"
def modulation(self, obj):
"""Отображает модуляцию из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param and param.modulation:
return param.modulation.name
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
if obj.parameter_obj.modulation:
return obj.parameter_obj.modulation.name
return "-"
modulation.short_description = "Модуляция"
def snr(self, obj):
"""Отображает отношение сигнал/шум из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.snr
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
return obj.parameter_obj.snr
return "-"
snr.short_description = "ОСШ"

View File

@@ -108,6 +108,12 @@ class NewEventForm(forms.Form):
})
)
class ParameterForm(forms.ModelForm):
"""
Форма для создания и редактирования параметров ВЧ загрузки.
Работает с одним экземпляром Parameter, связанным с ObjItem через OneToOne связь.
"""
class Meta:
model = Parameter
fields = [
@@ -115,22 +121,92 @@ class ParameterForm(forms.ModelForm):
'bod_velocity', 'modulation', 'snr', 'standard'
]
widgets = {
'id_satellite': forms.Select(attrs={'class': 'form-select'}, choices=[]),
'frequency': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
'freq_range': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
'bod_velocity': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
'snr': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
'polarization': forms.Select(attrs={'class': 'form-select'}, choices=[]),
'modulation': forms.Select(attrs={'class': 'form-select'}, choices=[]),
'standard': forms.Select(attrs={'class': 'form-select'}, choices=[]),
'id_satellite': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'frequency': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.000001',
'min': '0',
'max': '50000',
'placeholder': 'Введите частоту в МГц'
}),
'freq_range': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.000001',
'min': '0',
'max': '1000',
'placeholder': 'Введите полосу частот в МГц'
}),
'bod_velocity': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.001',
'min': '0',
'placeholder': 'Введите символьную скорость в БОД'
}),
'snr': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.001',
'min': '-50',
'max': '100',
'placeholder': 'Введите ОСШ в дБ'
}),
'polarization': forms.Select(attrs={'class': 'form-select'}),
'modulation': forms.Select(attrs={'class': 'form-select'}),
'standard': forms.Select(attrs={'class': 'form-select'}),
}
labels = {
'id_satellite': 'Спутник',
'frequency': 'Частота (МГц)',
'freq_range': 'Полоса частот (МГц)',
'polarization': 'Поляризация',
'bod_velocity': 'Символьная скорость (БОД)',
'modulation': 'Модуляция',
'snr': 'ОСШ (дБ)',
'standard': 'Стандарт',
}
help_texts = {
'frequency': 'Частота в диапазоне от 0 до 50000 МГц',
'freq_range': 'Полоса частот в диапазоне от 0 до 1000 МГц',
'bod_velocity': 'Символьная скорость должна быть положительной',
'snr': 'Отношение сигнал/шум в диапазоне от -50 до 100 дБ',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['id_satellite'].choices = [(s.id, s.name) for s in Satellite.objects.all()]
self.fields['polarization'].choices = [(p.id, p.name) for p in Polarization.objects.all()]
self.fields['modulation'].choices = [(m.id, m.name) for m in Modulation.objects.all()]
self.fields['standard'].choices = [(s.id, s.name) for s in Standard.objects.all()]
# Динамически загружаем choices для select полей
self.fields['id_satellite'].queryset = Satellite.objects.all().order_by('name')
self.fields['polarization'].queryset = Polarization.objects.all().order_by('name')
self.fields['modulation'].queryset = Modulation.objects.all().order_by('name')
self.fields['standard'].queryset = Standard.objects.all().order_by('name')
# Делаем спутник обязательным полем
self.fields['id_satellite'].required = True
def clean(self):
"""
Дополнительная валидация формы.
Проверяет соотношение между частотой, полосой частот и символьной скоростью.
"""
cleaned_data = super().clean()
frequency = cleaned_data.get('frequency')
freq_range = cleaned_data.get('freq_range')
bod_velocity = cleaned_data.get('bod_velocity')
# Проверка что частота больше полосы частот
if frequency and freq_range:
if freq_range > frequency:
self.add_error('freq_range', 'Полоса частот не может быть больше частоты')
# Проверка что символьная скорость соответствует полосе частот
if bod_velocity and freq_range:
if bod_velocity > freq_range * 1000000: # Конвертация МГц в Гц
self.add_error('bod_velocity', 'Символьная скорость не может превышать полосу частот')
return cleaned_data
class GeoForm(forms.ModelForm):
class Meta:
@@ -143,9 +219,49 @@ class GeoForm(forms.ModelForm):
}
class ObjItemForm(forms.ModelForm):
"""
Форма для создания и редактирования объектов (источников сигнала).
Работает с моделью ObjItem. Параметры ВЧ загрузки обрабатываются отдельно
через ParameterForm с использованием OneToOne связи.
"""
class Meta:
model = ObjItem
fields = ['name']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Введите название объекта',
'maxlength': '100'
}),
}
labels = {
'name': 'Название объекта',
}
help_texts = {
'name': 'Уникальное название объекта/источника сигнала',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Делаем поле name необязательным, так как оно может быть пустым
self.fields['name'].required = False
def clean_name(self):
"""
Валидация поля name.
Проверяет что название не состоит только из пробелов.
"""
name = self.cleaned_data.get('name')
if name:
# Удаляем лишние пробелы
name = name.strip()
# Проверяем что после удаления пробелов что-то осталось
if not name:
raise forms.ValidationError('Название не может состоять только из пробелов')
return name

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.7 on 2025-11-10 18:39
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0006_alter_customuser_options_alter_geo_options_and_more'),
]
operations = [
migrations.RemoveField(
model_name='parameter',
name='objitems',
),
migrations.AddField(
model_name='parameter',
name='objitem',
field=models.OneToOneField(blank=True, help_text='Связанный объект', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parameter_obj', to='mainapp.objitem', verbose_name='Объект'),
),
]

View File

@@ -243,11 +243,11 @@ class ObjItemQuerySet(models.QuerySet):
"updated_by__user",
"created_by__user",
"source_type_obj",
).prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
)
def recent(self, days=30):
@@ -449,8 +449,14 @@ class Parameter(models.Model):
verbose_name="Стандарт",
)
# id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True)
objitems = models.ManyToManyField(
ObjItem, related_name="parameters_obj", verbose_name="Источники", blank=True
objitem = models.OneToOneField(
ObjItem,
on_delete=models.CASCADE,
related_name="parameter_obj",
verbose_name="Объект",
null=True,
blank=True,
help_text="Связанный объект"
)
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True)
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True)

View File

@@ -116,72 +116,70 @@
</div>
</div>
<!-- ВЧ загрузки -->
<!-- ВЧ загрузка -->
<div class="form-section">
<div class="form-section-header">
<h4>ВЧ загрузка</h4>
</div>
{% for param in object.parameters_obj.all %}
<div class="dynamic-form" data-parameter-index="{{ forloop.counter0 }}">
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Спутник:</label>
<div class="readonly-field">{{ param.id_satellite.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Частота (МГц):</label>
<div class="readonly-field">{{ param.frequency|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Полоса (МГц):</label>
<div class="readonly-field">{{ param.freq_range|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Поляризация:</label>
<div class="readonly-field">{{ param.polarization.name|default:"-" }}</div>
</div>
{% if object.parameter_obj %}
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Спутник:</label>
<div class="readonly-field">{{ object.parameter_obj.id_satellite.name|default:"-" }}</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Символьная скорость:</label>
<div class="readonly-field">{{ param.bod_velocity|default:"-" }}</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Частота (МГц):</label>
<div class="readonly-field">{{ object.parameter_obj.frequency|default:"-" }}</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Модуляция:</label>
<div class="readonly-field">{{ param.modulation.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Полоса (МГц):</label>
<div class="readonly-field">{{ object.parameter_obj.freq_range|default:"-" }}</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">ОСШ:</label>
<div class="readonly-field">{{ param.snr|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Стандарт:</label>
<div class="readonly-field">{{ param.standard.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Поляризация:</label>
<div class="readonly-field">{{ object.parameter_obj.polarization.name|default:"-" }}</div>
</div>
</div>
</div>
{% empty %}
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Символьная скорость:</label>
<div class="readonly-field">{{ object.parameter_obj.bod_velocity|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Модуляция:</label>
<div class="readonly-field">{{ object.parameter_obj.modulation.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">ОСШ:</label>
<div class="readonly-field">{{ object.parameter_obj.snr|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Стандарт:</label>
<div class="readonly-field">{{ object.parameter_obj.standard.name|default:"-" }}</div>
</div>
</div>
</div>
{% else %}
<div class="mb-3">
<p>Нет данных о ВЧ загрузке</p>
</div>
{% endfor %}
{% endif %}
</div>
<!-- Блок с картой -->

View File

@@ -63,7 +63,7 @@
<h2>{% if object %}Редактировать объект: {{ object.name }}{% else %}Создать объект{% endif %}</h2>
<div>
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
<button type="submit" class="btn btn-primary btn-action">Сохранить</button>
<button type="submit" form="objitem-form" class="btn btn-primary btn-action">Сохранить</button>
{% if object %}
<a href="{% url 'mainapp:objitem_delete' object.id %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-action">Удалить</a>
{% endif %}
@@ -73,7 +73,7 @@
</div>
</div>
<form method="post">
<form method="post" id="objitem-form">
{% csrf_token %}
<!-- Основная информация -->
@@ -124,53 +124,41 @@
</div>
</div>
<!-- ВЧ загрузки -->
<!-- ВЧ загрузка -->
<div class="form-section">
<div class="form-section-header d-flex justify-content-between align-items-center">
<div class="form-section-header">
<h4>ВЧ загрузка</h4>
{% if not parameter_forms.forms.0.instance.pk %}
<button type="button" class="btn btn-sm btn-outline-primary" id="add-parameter">Добавить ВЧ загрузку</button>
{% endif %}
</div>
<div id="parameters-container">
{% for param_form in parameter_forms %}
{% comment %} <div class="dynamic-form" data-parameter-index="{{ forloop.counter0 }}"> {% endcomment %}
<div class="dynamic-form-header">
{% if parameter_forms.forms|length > 1 %}
<button type="button" class="btn btn-sm btn-outline-danger remove-parameter">Удалить</button>
{% endif %}
<div class="row">
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=parameter_form.id_satellite %}
</div>
<div class="row">
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=param_form.id_satellite %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=param_form.frequency %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=param_form.freq_range %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=param_form.polarization %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=parameter_form.frequency %}
</div>
<div class="row">
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=param_form.bod_velocity %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=param_form.modulation %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=param_form.snr %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=param_form.standard %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=parameter_form.freq_range %}
</div>
{% comment %} </div> {% endcomment %}
{% endfor %}
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=parameter_form.polarization %}
</div>
</div>
<div class="row">
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=parameter_form.bod_velocity %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=parameter_form.modulation %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=parameter_form.snr %}
</div>
<div class="col-md-3">
{% include 'mainapp/components/_form_field.html' with field=parameter_form.standard %}
</div>
</div>
</div>
</div>
@@ -348,42 +336,6 @@
<script>
// Динамическое добавление ВЧ загрузок
let parameterIndex = {{ parameter_forms|length }};
document.getElementById('add-parameter')?.addEventListener('click', function() {
const container = document.getElementById('parameters-container');
const template = document.querySelector('.dynamic-form');
if (template) {
const clone = template.cloneNode(true);
clone.querySelectorAll('[id]').forEach(el => {
el.id = el.id.replace(/-\d+-/g, `-${parameterIndex}-`);
});
clone.querySelectorAll('[name]').forEach(el => {
el.name = el.name.replace(/-\d+-/g, `-${parameterIndex}-`);
});
clone.querySelectorAll('[for]').forEach(el => {
el.htmlFor = el.htmlFor.replace(/-\d+-/g, `-${parameterIndex}-`);
});
clone.querySelector('.dynamic-form-header h5').textContent = `ВЧ загрузка #${parameterIndex + 1}`;
clone.dataset.parameterIndex = parameterIndex;
container.appendChild(clone);
parameterIndex++;
}
});
// Удаление ВЧ загрузок
document.addEventListener('click', function(e) {
if (e.target.classList.contains('remove-parameter')) {
if (document.querySelectorAll('.dynamic-form').length > 1) {
e.target.closest('.dynamic-form').remove();
} else {
alert('Должна быть хотя бы одна ВЧ загрузка');
}
}
});
document.addEventListener('DOMContentLoaded', function() {
// Инициализация карты
const map = L.map('map').setView([55.75, 37.62], 5);

View File

@@ -163,16 +163,6 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
source = stroka[1]["Объект наблюдения"]
user_to_use = current_user if current_user else CustomUser.objects.get(id=1)
vch_load_obj, _ = Parameter.objects.get_or_create(
id_satellite=sat,
polarization=polarization_obj,
frequency=freq,
freq_range=freq_line,
bod_velocity=v,
modulation=mod_obj,
snr=snr,
)
geo, _ = Geo.objects.get_or_create(
timestamp=timestamp,
coords=geo_point,
@@ -187,14 +177,40 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
geo.save()
geo.mirrors.set(Mirror.objects.filter(name__in=current_mirrors))
existing_obj_items = ObjItem.objects.filter(
parameters_obj=vch_load_obj, geo_obj=geo
# Check if ObjItem with same geo already exists
existing_obj_item = ObjItem.objects.filter(geo_obj=geo).first()
if existing_obj_item:
# Check if parameter with same values exists for this object
if (
hasattr(existing_obj_item, 'parameter_obj') and
existing_obj_item.parameter_obj and
existing_obj_item.parameter_obj.id_satellite == sat and
existing_obj_item.parameter_obj.polarization == polarization_obj and
existing_obj_item.parameter_obj.frequency == freq and
existing_obj_item.parameter_obj.freq_range == freq_line and
existing_obj_item.parameter_obj.bod_velocity == v and
existing_obj_item.parameter_obj.modulation == mod_obj and
existing_obj_item.parameter_obj.snr == snr
):
# Skip creating duplicate
continue
# Create new ObjItem and Parameter
obj_item = ObjItem.objects.create(name=source, created_by=user_to_use)
vch_load_obj = Parameter.objects.create(
id_satellite=sat,
polarization=polarization_obj,
frequency=freq,
freq_range=freq_line,
bod_velocity=v,
modulation=mod_obj,
snr=snr,
objitem=obj_item
)
if not existing_obj_items.exists():
obj_item = ObjItem.objects.create(name=source, created_by=user_to_use)
obj_item.parameters_obj.set([vch_load_obj])
geo.objitem = obj_item
geo.save()
geo.objitem = obj_item
geo.save()
def add_satellite_list():
@@ -316,14 +332,6 @@ def get_points_from_csv(file_content, current_user=None):
mir_3_obj, _ = Mirror.objects.get_or_create(name=row["mir_3"])
user_to_use = current_user if current_user else CustomUser.objects.get(id=1)
vch_load_obj, _ = Parameter.objects.get_or_create(
id_satellite=sat_obj,
polarization=pol_obj,
frequency=row["freq"],
freq_range=row["f_range"],
# defaults={'id_user_add': user_to_use}
)
geo_obj, _ = Geo.objects.get_or_create(
timestamp=row["time"],
coords=Point(row["lon"], row["lat"], srid=4326),
@@ -334,14 +342,34 @@ def get_points_from_csv(file_content, current_user=None):
)
geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
existing_obj_items = ObjItem.objects.filter(
parameters_obj=vch_load_obj, geo_obj=geo_obj
# Check if ObjItem with same geo already exists
existing_obj_item = ObjItem.objects.filter(geo_obj=geo_obj).first()
if existing_obj_item:
# Check if parameter with same values exists for this object
if (
hasattr(existing_obj_item, 'parameter_obj') and
existing_obj_item.parameter_obj and
existing_obj_item.parameter_obj.id_satellite == sat_obj and
existing_obj_item.parameter_obj.polarization == pol_obj and
existing_obj_item.parameter_obj.frequency == row["freq"] and
existing_obj_item.parameter_obj.freq_range == row["f_range"]
):
# Skip creating duplicate
continue
# Create new ObjItem and Parameter
obj_item = ObjItem.objects.create(name=row["obj"], created_by=user_to_use)
vch_load_obj = Parameter.objects.create(
id_satellite=sat_obj,
polarization=pol_obj,
frequency=row["freq"],
freq_range=row["f_range"],
objitem=obj_item
)
if not existing_obj_items.exists():
obj_item = ObjItem.objects.create(name=row["obj"], created_by=user_to_use)
obj_item.parameters_obj.set([vch_load_obj])
geo_obj.objitem = obj_item
geo_obj.save()
geo_obj.objitem = obj_item
geo_obj.save()
def get_vch_load_from_html(file, sat: Satellite) -> None:
@@ -598,29 +626,22 @@ def parse_pagination_params(
def get_first_param_subquery(field_name: str):
"""
Создает подзапрос для получения первого параметра объекта.
Возвращает F() выражение для доступа к полю параметра через OneToOne связь.
Используется для аннотации queryset с полями из связанной модели Parameter.
Возвращает значение указанного поля из первого параметра объекта.
После рефакторинга связи Parameter-ObjItem с ManyToMany на OneToOne,
эта функция упрощена для возврата прямого F() выражения вместо подзапроса.
Args:
field_name (str): Имя поля модели Parameter для извлечения.
Может включать связанные поля через __ (например, 'id_satellite__name').
Returns:
Subquery: Django Subquery объект для использования в annotate().
F: Django F() объект для использования в annotate().
Example:
>>> from django.db.models import Subquery, OuterRef
>>> freq_subq = get_first_param_subquery('frequency')
>>> objects = ObjItem.objects.annotate(first_freq=Subquery(freq_subq))
>>> freq_expr = get_first_param_subquery('frequency')
>>> objects = ObjItem.objects.annotate(first_freq=freq_expr)
>>> for obj in objects:
... print(obj.first_freq)
"""
from django.db.models import OuterRef
return (
Parameter.objects.filter(objitems=OuterRef("pk"))
.order_by("id")
.values(field_name)[:1]
)
return F(f"parameter_obj__{field_name}")

View File

@@ -1,30 +1,24 @@
# Standard library imports
from collections import defaultdict
from datetime import datetime
from io import BytesIO
# Django imports
from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.gis.geos import Point
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
from django.db import models
from django.db.models import OuterRef, Prefetch, Subquery
from django.forms import inlineformset_factory, modelformset_factory
from django.db.models import F
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.http import require_GET
from django.views.generic import (
CreateView,
DeleteView,
FormView,
TemplateView,
UpdateView,
)
@@ -45,18 +39,17 @@ from .forms import (
VchLinkForm,
)
from .mixins import CoordinateProcessingMixin, FormMessageMixin, RoleRequiredMixin
from .models import Geo, Modulation, ObjItem, Parameter, Polarization, Satellite
from .models import Geo, Modulation, ObjItem, Polarization, Satellite
from .utils import (
add_satellite_list,
compare_and_link_vch_load,
fill_data_from_df,
get_first_param_subquery,
get_points_from_csv,
get_vch_load_from_html,
kub_report,
parse_pagination_params,
)
from mapsapp.utils import parse_transponders_from_json, parse_transponders_from_xml
from mapsapp.utils import parse_transponders_from_xml
class AddSatellitesView(LoginRequiredMixin, View):
@@ -150,9 +143,12 @@ class LoadExcelDataView(LoginRequiredMixin, FormMessageMixin, FormView):
class GetLocationsView(LoginRequiredMixin, View):
def get(self, request, sat_id):
locations = (
ObjItem.objects.filter(parameters_obj__id_satellite=sat_id)
.select_related("geo_obj")
.prefetch_related("parameters_obj__polarization")
ObjItem.objects.filter(parameter_obj__id_satellite=sat_id)
.select_related(
"geo_obj",
"parameter_obj",
"parameter_obj__polarization",
)
)
if not locations.exists():
@@ -163,11 +159,10 @@ class GetLocationsView(LoginRequiredMixin, View):
if not hasattr(loc, "geo_obj") or not loc.geo_obj or not loc.geo_obj.coords:
continue
params = list(loc.parameters_obj.all())
if not params:
param = getattr(loc, 'parameter_obj', None)
if not param:
continue
param = params[0]
features.append(
{
"type": "Feature",
@@ -220,11 +215,12 @@ class ShowMapView(RoleRequiredMixin, View):
points = []
if ids:
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
locations = ObjItem.objects.filter(id__in=id_list).prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard",
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:
@@ -234,7 +230,9 @@ class ShowMapView(RoleRequiredMixin, View):
or not obj.geo_obj.coords
):
continue
param = obj.parameters_obj.get()
param = getattr(obj, 'parameter_obj', None)
if not param:
continue
points.append(
{
"name": f"{obj.name}",
@@ -265,11 +263,12 @@ class ShowSelectedObjectsMapView(LoginRequiredMixin, View):
points = []
if ids:
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
locations = ObjItem.objects.filter(id__in=id_list).prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard",
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:
@@ -279,7 +278,9 @@ class ShowSelectedObjectsMapView(LoginRequiredMixin, View):
or not obj.geo_obj.coords
):
continue
param = obj.parameters_obj.get()
param = getattr(obj, 'parameter_obj', None)
if not param:
continue
points.append(
{
"name": f"{obj.name}",
@@ -429,7 +430,7 @@ class DeleteSelectedObjectsView(RoleRequiredMixin, View):
class ObjItemListView(LoginRequiredMixin, View):
def get(self, request):
satellites = (
Satellite.objects.filter(parameters__objitems__isnull=False)
Satellite.objects.filter(parameters__objitem__isnull=False)
.distinct()
.only("id", "name")
.order_by("name")
@@ -472,32 +473,31 @@ class ObjItemListView(LoginRequiredMixin, View):
"geo_obj",
"updated_by__user",
"created_by__user",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
)
.prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard",
)
.filter(parameters_obj__id_satellite_id__in=selected_satellites)
.filter(parameter_obj__id_satellite_id__in=selected_satellites)
)
else:
objects = ObjItem.objects.select_related(
"geo_obj",
"updated_by__user",
"created_by__user",
).prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
)
if freq_min is not None and freq_min.strip() != "":
try:
freq_min_val = float(freq_min)
objects = objects.filter(
parameters_obj__frequency__gte=freq_min_val
parameter_obj__frequency__gte=freq_min_val
)
except ValueError:
pass
@@ -505,7 +505,7 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
freq_max_val = float(freq_max)
objects = objects.filter(
parameters_obj__frequency__lte=freq_max_val
parameter_obj__frequency__lte=freq_max_val
)
except ValueError:
pass
@@ -514,7 +514,7 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
range_min_val = float(range_min)
objects = objects.filter(
parameters_obj__freq_range__gte=range_min_val
parameter_obj__freq_range__gte=range_min_val
)
except ValueError:
pass
@@ -522,7 +522,7 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
range_max_val = float(range_max)
objects = objects.filter(
parameters_obj__freq_range__lte=range_max_val
parameter_obj__freq_range__lte=range_max_val
)
except ValueError:
pass
@@ -530,13 +530,13 @@ class ObjItemListView(LoginRequiredMixin, View):
if snr_min is not None and snr_min.strip() != "":
try:
snr_min_val = float(snr_min)
objects = objects.filter(parameters_obj__snr__gte=snr_min_val)
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(parameters_obj__snr__lte=snr_max_val)
objects = objects.filter(parameter_obj__snr__lte=snr_max_val)
except ValueError:
pass
@@ -544,7 +544,7 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
bod_min_val = float(bod_min)
objects = objects.filter(
parameters_obj__bod_velocity__gte=bod_min_val
parameter_obj__bod_velocity__gte=bod_min_val
)
except ValueError:
pass
@@ -552,19 +552,19 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
bod_max_val = float(bod_max)
objects = objects.filter(
parameters_obj__bod_velocity__lte=bod_max_val
parameter_obj__bod_velocity__lte=bod_max_val
)
except ValueError:
pass
if selected_modulations:
objects = objects.filter(
parameters_obj__modulation__id__in=selected_modulations
parameter_obj__modulation__id__in=selected_modulations
)
if selected_polarizations:
objects = objects.filter(
parameters_obj__polarization__id__in=selected_polarizations
parameter_obj__polarization__id__in=selected_polarizations
)
if has_kupsat == "1":
@@ -609,22 +609,14 @@ class ObjItemListView(LoginRequiredMixin, View):
else:
selected_sat_id = None
first_param_freq_subq = get_first_param_subquery("frequency")
first_param_range_subq = get_first_param_subquery("freq_range")
first_param_snr_subq = get_first_param_subquery("snr")
first_param_bod_subq = get_first_param_subquery("bod_velocity")
first_param_sat_name_subq = get_first_param_subquery("id_satellite__name")
first_param_pol_name_subq = get_first_param_subquery("polarization__name")
first_param_mod_name_subq = get_first_param_subquery("modulation__name")
objects = objects.annotate(
first_param_freq=Subquery(first_param_freq_subq),
first_param_range=Subquery(first_param_range_subq),
first_param_snr=Subquery(first_param_snr_subq),
first_param_bod=Subquery(first_param_bod_subq),
first_param_sat_name=Subquery(first_param_sat_name_subq),
first_param_pol_name=Subquery(first_param_pol_name_subq),
first_param_mod_name=Subquery(first_param_mod_name_subq),
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 = {
@@ -664,11 +656,7 @@ class ObjItemListView(LoginRequiredMixin, View):
processed_objects = []
for obj in page_obj:
param = None
if hasattr(obj, "parameters_obj") and obj.parameters_obj.all():
param_list = list(obj.parameters_obj.all())
if param_list:
param = param_list[0]
param = getattr(obj, 'parameter_obj', None)
geo_coords = "-"
geo_timestamp = "-"
@@ -874,40 +862,33 @@ class ObjItemFormView(
# Сохраняем параметры возврата для кнопки "Назад"
context["return_params"] = self.request.GET.get('return_params', '')
ParameterFormSet = modelformset_factory(
Parameter,
form=ParameterForm,
extra=self.get_parameter_formset_extra(),
can_delete=True,
)
if self.object:
parameter_queryset = self.object.parameters_obj.all()
context["parameter_forms"] = ParameterFormSet(
queryset=parameter_queryset, prefix="parameters"
# Работаем с одной формой параметра вместо 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"
)
if hasattr(self.object, "geo_obj"):
context["geo_form"] = GeoForm(
instance=self.object.geo_obj, prefix="geo"
)
else:
context["geo_form"] = GeoForm(prefix="geo")
else:
context["parameter_forms"] = ParameterFormSet(
queryset=Parameter.objects.none(), prefix="parameters"
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 get_parameter_formset_extra(self):
"""Возвращает количество дополнительных форм для параметров."""
return 0 if self.object else 1
def form_valid(self, form):
context = self.get_context_data()
parameter_forms = context["parameter_forms"]
# Получаем форму параметра
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")
@@ -919,17 +900,26 @@ class ObjItemFormView(
self.set_user_fields()
self.object.save()
# Сохраняем связанные параметры
if parameter_forms.is_valid():
self.save_parameters(parameter_forms)
# Сохраняем связанный параметр
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)
# Сохраняем геоданные
if geo_form.is_valid():
self.save_geo_data(geo_form)
else:
context = self.get_context_data()
context.update({
'form': form,
'parameter_forms': parameter_forms,
'parameter_form': parameter_form,
'geo_form': geo_form,
})
return self.render_to_response(context)
@@ -940,51 +930,12 @@ class ObjItemFormView(
"""Устанавливает поля пользователя для объекта."""
raise NotImplementedError("Subclasses must implement set_user_fields()")
def save_parameters(self, parameter_forms):
"""Сохраняет параметры объекта с проверкой дубликатов."""
instances = parameter_forms.save(commit=False)
# Обрабатываем удаленные параметры
for deleted_obj in parameter_forms.deleted_objects:
# Отвязываем параметр от объекта
deleted_obj.objitems.remove(self.object)
# Если параметр больше не связан ни с одним объектом, удаляем его
if not deleted_obj.objitems.exists():
deleted_obj.delete()
for instance in instances:
# Проверяем, существует ли уже такая ВЧ загрузка
existing_param = Parameter.objects.filter(
id_satellite=instance.id_satellite,
polarization=instance.polarization,
frequency=instance.frequency,
freq_range=instance.freq_range,
bod_velocity=instance.bod_velocity,
modulation=instance.modulation,
snr=instance.snr,
standard=instance.standard,
).exclude(pk=instance.pk if instance.pk else None).first()
if existing_param:
# Если найден дубликат, удаляем старую запись из объекта
if instance.pk:
# Отвязываем старый параметр от объекта
instance.objitems.remove(self.object)
# Если старый параметр больше не связан ни с одним объектом, удаляем его
if not instance.objitems.exists():
instance.delete()
# Используем существующий параметр
self.link_parameter_to_object(existing_param)
else:
# Сохраняем новый параметр
instance.save()
self.link_parameter_to_object(instance)
def link_parameter_to_object(self, parameter):
"""Связывает параметр с объектом."""
raise NotImplementedError(
"Subclasses must implement link_parameter_to_object()"
)
def save_parameter(self, parameter_form):
"""Сохраняет параметр объекта через OneToOne связь."""
if parameter_form.is_valid():
instance = parameter_form.save(commit=False)
instance.objitem = self.object
instance.save()
def save_geo_data(self, geo_form):
"""Сохраняет геоданные объекта."""
@@ -1019,11 +970,6 @@ class ObjItemUpdateView(ObjItemFormView):
def set_user_fields(self):
self.object.updated_by = self.request.user.customuser
def link_parameter_to_object(self, parameter):
# Добавляем объект к параметру, если его там еще нет
if self.object not in parameter.objitems.all():
parameter.objitems.add(self.object)
class ObjItemCreateView(ObjItemFormView, CreateView):
"""Представление для создания ObjItem."""
@@ -1034,9 +980,6 @@ class ObjItemCreateView(ObjItemFormView, CreateView):
self.object.created_by = self.request.user.customuser
self.object.updated_by = self.request.user.customuser
def link_parameter_to_object(self, parameter):
parameter.objitems.add(self.object)
class ObjItemDeleteView(RoleRequiredMixin, FormMessageMixin, DeleteView):
model = ObjItem
@@ -1066,11 +1009,11 @@ class ObjItemDetailView(LoginRequiredMixin, View):
'geo_obj',
'updated_by__user',
'created_by__user',
).prefetch_related(
'parameters_obj__id_satellite',
'parameters_obj__polarization',
'parameters_obj__modulation',
'parameters_obj__standard',
'parameter_obj',
'parameter_obj__id_satellite',
'parameter_obj__polarization',
'parameter_obj__modulation',
'parameter_obj__standard',
).first()
if not obj:

View File

@@ -19,42 +19,42 @@ services:
networks:
- app-network
web:
build:
context: ./dbapp
dockerfile: Dockerfile
container_name: django-app-dev
restart: unless-stopped
environment:
- DEBUG=True
- ENVIRONMENT=development
- DJANGO_SETTINGS_MODULE=dbapp.settings.development
- SECRET_KEY=django-insecure-dev-key-change-in-production
- DB_ENGINE=django.contrib.gis.db.backends.postgis
- DB_NAME=geodb
- DB_USER=geralt
- DB_PASSWORD=123456
- DB_HOST=db
- DB_PORT=5432
- ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
ports:
- "8000:8000"
volumes:
# Монтируем только код приложения, не весь проект
- ./dbapp/dbapp:/app/dbapp
- ./dbapp/mainapp:/app/mainapp
- ./dbapp/mapsapp:/app/mapsapp
- ./dbapp/lyngsatapp:/app/lyngsatapp
- ./dbapp/static:/app/static
- ./dbapp/manage.py:/app/manage.py
- static_volume_dev:/app/staticfiles
- media_volume_dev:/app/media
- logs_volume_dev:/app/logs
depends_on:
db:
condition: service_healthy
networks:
- app-network
# web:
# build:
# context: ./dbapp
# dockerfile: Dockerfile
# container_name: django-app-dev
# restart: unless-stopped
# environment:
# - DEBUG=True
# - ENVIRONMENT=development
# - DJANGO_SETTINGS_MODULE=dbapp.settings.development
# - SECRET_KEY=django-insecure-dev-key-change-in-production
# - DB_ENGINE=django.contrib.gis.db.backends.postgis
# - DB_NAME=geodb
# - DB_USER=geralt
# - DB_PASSWORD=123456
# - DB_HOST=db
# - DB_PORT=5432
# - ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
# ports:
# - "8000:8000"
# volumes:
# # Монтируем только код приложения, не весь проект
# - ./dbapp/dbapp:/app/dbapp
# - ./dbapp/mainapp:/app/mainapp
# - ./dbapp/mapsapp:/app/mapsapp
# - ./dbapp/lyngsatapp:/app/lyngsatapp
# - ./dbapp/static:/app/static
# - ./dbapp/manage.py:/app/manage.py
# - static_volume_dev:/app/staticfiles
# - media_volume_dev:/app/media
# - logs_volume_dev:/app/logs
# depends_on:
# db:
# condition: service_healthy
# networks:
# - app-network
# tileserver:
# image: maptiler/tileserver-gl:latest
@@ -72,9 +72,9 @@ services:
volumes:
postgres_data_dev:
static_volume_dev:
media_volume_dev:
logs_volume_dev:
# static_volume_dev:
# media_volume_dev:
# logs_volume_dev:
# tileserver_config_dev:
networks: