Доделал страницу с Кубсатами
This commit is contained in:
@@ -54,8 +54,23 @@
|
|||||||
- Количество точек автоматически пересчитывается при удалении строк
|
- Количество точек автоматически пересчитывается при удалении строк
|
||||||
- Таблица имеет фиксированную высоту с прокруткой и sticky заголовок
|
- Таблица имеет фиксированную высоту с прокруткой и sticky заголовок
|
||||||
|
|
||||||
### Выделение по дате
|
### Двухэтапная фильтрация
|
||||||
Строки, у которых дата ГЛ попадает в выбранный диапазон дат, **выделяются зеленым цветом**. При этом все объекты остаются в таблице, независимо от фильтра по дате.
|
|
||||||
|
Фильтрация происходит в два этапа:
|
||||||
|
|
||||||
|
**Этап 1: Фильтрация по дате ГЛ**
|
||||||
|
- Если задан диапазон дат (от/до), отображаются только те точки, у которых дата ГЛ попадает в этот диапазон
|
||||||
|
- Точки без даты ГЛ исключаются, если фильтр по дате задан
|
||||||
|
- Если фильтр не задан, показываются все точки
|
||||||
|
|
||||||
|
**Этап 2: Фильтрация по количеству точек**
|
||||||
|
- Применяется к уже отфильтрованным по дате точкам
|
||||||
|
- Варианты:
|
||||||
|
- "Все" - показываются источники с любым количеством точек
|
||||||
|
- "1" - только источники с ровно 1 точкой (после фильтрации по дате)
|
||||||
|
- "2 и более" - только источники с 2 и более точками (после фильтрации по дате)
|
||||||
|
|
||||||
|
**Важно**: Фильтр по количеству точек учитывает только те точки, которые прошли фильтрацию по дате!
|
||||||
|
|
||||||
### Управление данными в таблице
|
### Управление данными в таблице
|
||||||
|
|
||||||
@@ -63,10 +78,7 @@
|
|||||||
- **Кнопка с иконкой корзины** (для каждой точки) - удаляет конкретную точку (ObjItem) из таблицы
|
- **Кнопка с иконкой корзины** (для каждой точки) - удаляет конкретную точку (ObjItem) из таблицы
|
||||||
- **Кнопка с иконкой заполненной корзины** (для первой точки источника) - удаляет все точки источника (Source) из таблицы
|
- **Кнопка с иконкой заполненной корзины** (для первой точки источника) - удаляет все точки источника (Source) из таблицы
|
||||||
|
|
||||||
**Кнопка "Оставить только подходящие по дате":**
|
|
||||||
- Удаляет из таблицы все точки, которые НЕ подходят по заданному диапазону дат
|
|
||||||
- Оставляет только зеленые (выделенные) строки
|
|
||||||
- Запрашивает подтверждение перед удалением
|
|
||||||
|
|
||||||
**Важно**: Все удаления происходят только из таблицы, **БЕЗ удаления из базы данных**. Это позволяет пользователю исключить ненужные записи перед экспортом.
|
**Важно**: Все удаления происходят только из таблицы, **БЕЗ удаления из базы данных**. Это позволяет пользователю исключить ненужные записи перед экспортом.
|
||||||
|
|
||||||
@@ -83,7 +95,7 @@
|
|||||||
8. **Обратный канал, МГц** - частота источника
|
8. **Обратный канал, МГц** - частота источника
|
||||||
9. **Перенос** - из объекта Transponder
|
9. **Перенос** - из объекта Transponder
|
||||||
10. **Получено координат, раз** - количество точек (ObjItem), оставшихся в таблице для данного источника
|
10. **Получено координат, раз** - количество точек (ObjItem), оставшихся в таблице для данного источника
|
||||||
11. **Дата** - пока заполняется как "-"
|
11. **Период получения координат** - диапазон дат ГЛ в формате "5.11.2025-15.11.2025" (от самой ранней до самой поздней даты среди точек источника). Если все точки имеют одну дату, показывается только одна дата.
|
||||||
12. **Зеркала** - все имена зеркал через перенос строки (из оставшихся точек)
|
12. **Зеркала** - все имена зеркал через перенос строки (из оставшихся точек)
|
||||||
13. **СКО, км** - не заполняется
|
13. **СКО, км** - не заполняется
|
||||||
14. **Примечание** - не заполняется
|
14. **Примечание** - не заполняется
|
||||||
@@ -123,14 +135,13 @@ queryset = Source.objects.select_related('info').prefetch_related(
|
|||||||
|
|
||||||
1. Откройте страницу "Кубсат" из навигационного меню
|
1. Откройте страницу "Кубсат" из навигационного меню
|
||||||
2. Выберите нужные фильтры (спутники, поляризация, частота и т.д.)
|
2. Выберите нужные фильтры (спутники, поляризация, частота и т.д.)
|
||||||
3. Опционально укажите диапазон дат для выделения подходящих точек
|
3. Опционально укажите диапазон дат для фильтрации точек по дате ГЛ (Этап 1)
|
||||||
4. Нажмите "Применить фильтры"
|
4. Опционально выберите количество точек (1 или 2+) - применяется к отфильтрованным по дате точкам (Этап 2)
|
||||||
5. В таблице отобразятся все точки (ObjItem) сгруппированные по источникам (Source)
|
5. Нажмите "Применить фильтры"
|
||||||
6. Точки, подходящие по дате, будут выделены зеленым цветом
|
6. В таблице отобразятся точки (ObjItem) сгруппированные по источникам (Source)
|
||||||
7. Опционально нажмите "Оставить только подходящие по дате" для быстрого удаления неподходящих точек
|
7. При необходимости удалите отдельные точки или целые объекты кнопками в колонке "Действия"
|
||||||
8. При необходимости удалите отдельные точки или целые объекты кнопками в колонке "Действия"
|
8. Нажмите "Экспорт в Excel" для скачивания файла с оставшимися данными
|
||||||
9. Нажмите "Экспорт в Excel" для скачивания файла с оставшимися данными
|
9. Форма не сбрасывается после экспорта - можно продолжить работу
|
||||||
10. Форма не сбрасывается после экспорта - можно продолжить работу
|
|
||||||
|
|
||||||
## Примечания
|
## Примечания
|
||||||
|
|
||||||
@@ -138,5 +149,6 @@ queryset = Source.objects.select_related('info').prefetch_related(
|
|||||||
- Удаление точек/объектов из таблицы не влияет на базу данных
|
- Удаление точек/объектов из таблицы не влияет на базу данных
|
||||||
- Экспортируются только оставшиеся в таблице точки
|
- Экспортируются только оставшиеся в таблице точки
|
||||||
- Координаты в Excel рассчитываются как инкрементальное среднее из оставшихся точек
|
- Координаты в Excel рассчитываются как инкрементальное среднее из оставшихся точек
|
||||||
- Фильтр по дате не скрывает объекты, а только выделяет их цветом
|
- Фильтр по дате скрывает неподходящие точки (не показывает их в таблице)
|
||||||
- Каждая строка таблицы = одна точка (ObjItem), строки группируются по источникам (Source)
|
- Каждая строка таблицы = одна точка (ObjItem), строки группируются по источникам (Source)
|
||||||
|
- Количество точек в колонке "Кол-во точек" автоматически пересчитывается при удалении строк
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ class SourceForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Source
|
model = Source
|
||||||
fields = ['info'] # Добавляем поле info
|
fields = ['info']
|
||||||
widgets = {
|
widgets = {
|
||||||
'info': forms.Select(attrs={
|
'info': forms.Select(attrs={
|
||||||
'class': 'form-select',
|
'class': 'form-select',
|
||||||
@@ -555,18 +555,18 @@ class KubsatFilterForm(forms.Form):
|
|||||||
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '5'})
|
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '5'})
|
||||||
)
|
)
|
||||||
|
|
||||||
band = forms.ModelChoiceField(
|
band = forms.ModelMultipleChoiceField(
|
||||||
queryset=None,
|
queryset=None,
|
||||||
label='Полоса спутника',
|
label='Диапазоны работы спутника',
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Select(attrs={'class': 'form-select'})
|
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '4'})
|
||||||
)
|
)
|
||||||
|
|
||||||
polarization = forms.ModelMultipleChoiceField(
|
polarization = forms.ModelMultipleChoiceField(
|
||||||
queryset=Polarization.objects.all().order_by('name'),
|
queryset=Polarization.objects.all().order_by('name'),
|
||||||
label='Поляризация',
|
label='Поляризация',
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '3'})
|
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '4'})
|
||||||
)
|
)
|
||||||
|
|
||||||
frequency_min = forms.FloatField(
|
frequency_min = forms.FloatField(
|
||||||
@@ -597,7 +597,7 @@ class KubsatFilterForm(forms.Form):
|
|||||||
queryset=Modulation.objects.all().order_by('name'),
|
queryset=Modulation.objects.all().order_by('name'),
|
||||||
label='Модуляция',
|
label='Модуляция',
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '3'})
|
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '4'})
|
||||||
)
|
)
|
||||||
|
|
||||||
object_type = forms.ModelMultipleChoiceField(
|
object_type = forms.ModelMultipleChoiceField(
|
||||||
@@ -624,22 +624,22 @@ class KubsatFilterForm(forms.Form):
|
|||||||
|
|
||||||
# Фиктивные фильтры
|
# Фиктивные фильтры
|
||||||
has_plans = forms.ChoiceField(
|
has_plans = forms.ChoiceField(
|
||||||
choices=[('', 'Все'), ('yes', 'Да'), ('no', 'Нет')],
|
choices=[('', 'Неважно'), ('yes', 'Да'), ('no', 'Нет')],
|
||||||
label='Планы на',
|
label='Планы на Кубсат',
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.RadioSelect()
|
widget=forms.RadioSelect()
|
||||||
)
|
)
|
||||||
|
|
||||||
success_1 = forms.ChoiceField(
|
success_1 = forms.ChoiceField(
|
||||||
choices=[('', 'Все'), ('yes', 'Да'), ('no', 'Нет')],
|
choices=[('', 'Неважно'), ('yes', 'Да'), ('no', 'Нет')],
|
||||||
label='Успех 1',
|
label='ГСО успешно?',
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.RadioSelect()
|
widget=forms.RadioSelect()
|
||||||
)
|
)
|
||||||
|
|
||||||
success_2 = forms.ChoiceField(
|
success_2 = forms.ChoiceField(
|
||||||
choices=[('', 'Все'), ('yes', 'Да'), ('no', 'Нет')],
|
choices=[('', 'Неважно'), ('yes', 'Да'), ('no', 'Нет')],
|
||||||
label='Успех 2',
|
label='Кубсат успешно?',
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.RadioSelect()
|
widget=forms.RadioSelect()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -37,11 +37,11 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mainapp:kubsat' %}">Кубсат</a>
|
<a class="nav-link" href="{% url 'mainapp:kubsat' %}">Кубсат</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<!-- <li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mapsapp:3dmap' %}">3D карта</a>
|
<a class="nav-link" href="{% url 'mapsapp:3dmap' %}">3D карта</a>
|
||||||
</li>
|
</li> -->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mapsapp:2dmap' %}">2D карта</a>
|
<a class="nav-link" href="{% url 'mapsapp:2dmap' %}">Карта</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'admin:index' %}">Админ панель</a>
|
<a class="nav-link" href="{% url 'admin:index' %}">Админ панель</a>
|
||||||
|
|||||||
@@ -23,6 +23,12 @@
|
|||||||
<!-- Спутники -->
|
<!-- Спутники -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="{{ form.satellites.id_for_label }}" class="form-label">{{ form.satellites.label }}</label>
|
<label for="{{ form.satellites.id_for_label }}" class="form-label">{{ form.satellites.label }}</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('satellites', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('satellites', false)">Снять</button>
|
||||||
|
</div>
|
||||||
{{ form.satellites }}
|
{{ form.satellites }}
|
||||||
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,19 +36,40 @@
|
|||||||
<!-- Полоса спутника -->
|
<!-- Полоса спутника -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="{{ form.band.id_for_label }}" class="form-label">{{ form.band.label }}</label>
|
<label for="{{ form.band.id_for_label }}" class="form-label">{{ form.band.label }}</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('band', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('band', false)">Снять</button>
|
||||||
|
</div>
|
||||||
{{ form.band }}
|
{{ form.band }}
|
||||||
|
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Поляризация -->
|
<!-- Поляризация -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="{{ form.polarization.id_for_label }}" class="form-label">{{ form.polarization.label }}</label>
|
<label for="{{ form.polarization.id_for_label }}" class="form-label">{{ form.polarization.label }}</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('polarization', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('polarization', false)">Снять</button>
|
||||||
|
</div>
|
||||||
{{ form.polarization }}
|
{{ form.polarization }}
|
||||||
|
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Модуляция -->
|
<!-- Модуляция -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="{{ form.modulation.id_for_label }}" class="form-label">{{ form.modulation.label }}</label>
|
<label for="{{ form.modulation.id_for_label }}" class="form-label">{{ form.modulation.label }}</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('modulation', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('modulation', false)">Снять</button>
|
||||||
|
</div>
|
||||||
{{ form.modulation }}
|
{{ form.modulation }}
|
||||||
|
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -70,12 +97,19 @@
|
|||||||
<!-- Тип объекта -->
|
<!-- Тип объекта -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="{{ form.object_type.id_for_label }}" class="form-label">{{ form.object_type.label }}</label>
|
<label for="{{ form.object_type.id_for_label }}" class="form-label">{{ form.object_type.label }}</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('object_type', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('object_type', false)">Снять</button>
|
||||||
|
</div>
|
||||||
{{ form.object_type }}
|
{{ form.object_type }}
|
||||||
|
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Принадлежность объекта (заглушка) -->
|
<!-- Принадлежность объекта (заглушка) -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="{{ form.object_ownership.id_for_label }}" class="form-label">{{ form.object_ownership.label }} <span class="badge bg-secondary">Заглушка</span></label>
|
<label for="{{ form.object_ownership.id_for_label }}" class="form-label">{{ form.object_ownership.label }} (не работает)</label>
|
||||||
{{ form.object_ownership }}
|
{{ form.object_ownership }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,7 +132,7 @@
|
|||||||
|
|
||||||
<!-- Планы на (фиктивный) -->
|
<!-- Планы на (фиктивный) -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label class="form-label">{{ form.has_plans.label }} <span class="badge bg-secondary">Фиктивный</span></label>
|
<label class="form-label">{{ form.has_plans.label }} (не работает)</label>
|
||||||
<div>
|
<div>
|
||||||
{% for radio in form.has_plans %}
|
{% for radio in form.has_plans %}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
@@ -113,7 +147,7 @@
|
|||||||
|
|
||||||
<!-- Успех 1 (фиктивный) -->
|
<!-- Успех 1 (фиктивный) -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label class="form-label">{{ form.success_1.label }} <span class="badge bg-secondary">Фиктивный</span></label>
|
<label class="form-label">{{ form.success_1.label }} (не рабльает)</label>
|
||||||
<div>
|
<div>
|
||||||
{% for radio in form.success_1 %}
|
{% for radio in form.success_1 %}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
@@ -128,7 +162,7 @@
|
|||||||
|
|
||||||
<!-- Успех 2 (фиктивный) -->
|
<!-- Успех 2 (фиктивный) -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label class="form-label">{{ form.success_2.label }} <span class="badge bg-secondary">Фиктивный</span></label>
|
<label class="form-label">{{ form.success_2.label }} (не работает)</label>
|
||||||
<div>
|
<div>
|
||||||
{% for radio in form.success_2 %}
|
{% for radio in form.success_2 %}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
@@ -145,7 +179,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Диапазон дат (фиктивный) -->
|
<!-- Диапазон дат (фиктивный) -->
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label">Диапазон дат <span class="badge bg-secondary">Фиктивный</span></label>
|
<label class="form-label">Диапазон дат ГЛ:</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
{{ form.date_from }}
|
{{ form.date_from }}
|
||||||
<span class="input-group-text">—</span>
|
<span class="input-group-text">—</span>
|
||||||
@@ -164,7 +198,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Кнопки экспорта и фильтрации -->
|
<!-- Кнопка экспорта и статистика -->
|
||||||
{% if sources_with_date_info %}
|
{% if sources_with_date_info %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@@ -174,10 +208,10 @@
|
|||||||
<button type="button" class="btn btn-success" onclick="exportToExcel()">
|
<button type="button" class="btn btn-success" onclick="exportToExcel()">
|
||||||
<i class="bi bi-file-earmark-excel"></i> Экспорт в Excel
|
<i class="bi bi-file-earmark-excel"></i> Экспорт в Excel
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-warning" onclick="filterByDateMatch()">
|
<span class="text-muted" id="statsCounter">
|
||||||
<i class="bi bi-funnel"></i> Оставить только подходящие по дате
|
Найдено объектов: {{ sources_with_date_info|length }},
|
||||||
</button>
|
точек: {% for source_data in sources_with_date_info %}{{ source_data.objitems_data|length }}{% if not forloop.last %}+{% endif %}{% endfor %}
|
||||||
<span class="ms-auto text-muted"></span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -195,7 +229,7 @@
|
|||||||
<table class="table table-striped table-hover table-sm table-bordered" style="font-size: 0.85rem;" id="resultsTable">
|
<table class="table table-striped table-hover table-sm table-bordered" style="font-size: 0.85rem;" id="resultsTable">
|
||||||
<thead class="table-dark sticky-top">
|
<thead class="table-dark sticky-top">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="min-width: 80px;">ID Source</th>
|
<th style="min-width: 80px;">ID объекта</th>
|
||||||
<th style="min-width: 120px;">Тип объекта</th>
|
<th style="min-width: 120px;">Тип объекта</th>
|
||||||
<th class="text-center" style="min-width: 100px;">Кол-во точек</th>
|
<th class="text-center" style="min-width: 100px;">Кол-во точек</th>
|
||||||
<th style="min-width: 120px;">Имя точки</th>
|
<th style="min-width: 120px;">Имя точки</th>
|
||||||
@@ -214,7 +248,6 @@
|
|||||||
{% for objitem_data in source_data.objitems_data %}
|
{% for objitem_data in source_data.objitems_data %}
|
||||||
<tr data-source-id="{{ source_data.source.id }}"
|
<tr data-source-id="{{ source_data.source.id }}"
|
||||||
data-objitem-id="{{ objitem_data.objitem.id }}"
|
data-objitem-id="{{ objitem_data.objitem.id }}"
|
||||||
class="{% if objitem_data.matches_date %}table-success{% endif %}"
|
|
||||||
data-matches-date="{{ objitem_data.matches_date|yesno:'true,false' }}"
|
data-matches-date="{{ objitem_data.matches_date|yesno:'true,false' }}"
|
||||||
data-is-first-in-source="{% if forloop.first %}true{% else %}false{% endif %}">
|
data-is-first-in-source="{% if forloop.first %}true{% else %}false{% endif %}">
|
||||||
|
|
||||||
@@ -251,7 +284,7 @@
|
|||||||
<!-- Частота -->
|
<!-- Частота -->
|
||||||
<td>
|
<td>
|
||||||
{% if objitem_data.objitem.parameter_obj %}
|
{% if objitem_data.objitem.parameter_obj %}
|
||||||
{{ objitem_data.objitem.parameter_obj.frequency|default:"-" }}
|
{{ objitem_data.objitem.parameter_obj.frequency|default:"-"|floatformat:3 }}
|
||||||
{% else %}
|
{% else %}
|
||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -260,7 +293,7 @@
|
|||||||
<!-- Полоса -->
|
<!-- Полоса -->
|
||||||
<td>
|
<td>
|
||||||
{% if objitem_data.objitem.parameter_obj %}
|
{% if objitem_data.objitem.parameter_obj %}
|
||||||
{{ objitem_data.objitem.parameter_obj.freq_range|default:"-" }}
|
{{ objitem_data.objitem.parameter_obj.freq_range|default:"-"|floatformat:3 }}
|
||||||
{% else %}
|
{% else %}
|
||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -287,7 +320,7 @@
|
|||||||
<!-- Координаты ГЛ -->
|
<!-- Координаты ГЛ -->
|
||||||
<td>
|
<td>
|
||||||
{% if objitem_data.objitem.geo_obj and objitem_data.objitem.geo_obj.coords %}
|
{% if objitem_data.objitem.geo_obj and objitem_data.objitem.geo_obj.coords %}
|
||||||
{{ objitem_data.objitem.geo_obj.coords.y|floatformat:6 }}, {{ objitem_data.objitem.geo_obj.coords.x|floatformat:6 }}
|
{{ objitem_data.objitem.geo_obj.coords.y }}, {{ objitem_data.objitem.geo_obj.coords.x }}
|
||||||
{% else %}
|
{% else %}
|
||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -439,70 +472,18 @@ function removeSource(button) {
|
|||||||
|
|
||||||
function updateCounter() {
|
function updateCounter() {
|
||||||
const rows = document.querySelectorAll('#resultsTable tbody tr');
|
const rows = document.querySelectorAll('#resultsTable tbody tr');
|
||||||
const counter = document.querySelector('.text-muted');
|
const counter = document.getElementById('statsCounter');
|
||||||
if (counter) {
|
if (counter) {
|
||||||
// Подсчитываем уникальные источники
|
// Подсчитываем уникальные источники
|
||||||
const uniqueSources = new Set();
|
const uniqueSources = new Set();
|
||||||
let matchingCount = 0;
|
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
uniqueSources.add(row.dataset.sourceId);
|
uniqueSources.add(row.dataset.sourceId);
|
||||||
if (row.dataset.matchesDate === 'true') {
|
|
||||||
matchingCount++;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
counter.textContent = `Найдено объектов: ${uniqueSources.size}, точек: ${rows.length} (подходящих по дате: ${matchingCount})`;
|
counter.textContent = `Найдено объектов: ${uniqueSources.size}, точек: ${rows.length}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterByDateMatch() {
|
|
||||||
// Удаляем все строки, которые не подходят по дате
|
|
||||||
const rows = Array.from(document.querySelectorAll('#resultsTable tbody tr'));
|
|
||||||
const rowsToRemove = rows.filter(row => row.dataset.matchesDate !== 'true');
|
|
||||||
|
|
||||||
if (rowsToRemove.length === 0) {
|
|
||||||
alert('Все точки уже подходят по дате или фильтр по дате не задан');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirm(`Удалить ${rowsToRemove.length} точек, не подходящих по дате?`)) {
|
|
||||||
// Группируем строки по источникам
|
|
||||||
const sourceGroups = {};
|
|
||||||
rows.forEach(row => {
|
|
||||||
const sourceId = row.dataset.sourceId;
|
|
||||||
if (!sourceGroups[sourceId]) {
|
|
||||||
sourceGroups[sourceId] = [];
|
|
||||||
}
|
|
||||||
sourceGroups[sourceId].push(row);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обрабатываем каждый источник отдельно
|
|
||||||
Object.keys(sourceGroups).forEach(sourceId => {
|
|
||||||
const sourceRows = sourceGroups[sourceId];
|
|
||||||
const rowsToKeep = sourceRows.filter(row => row.dataset.matchesDate === 'true');
|
|
||||||
const rowsToDelete = sourceRows.filter(row => row.dataset.matchesDate !== 'true');
|
|
||||||
|
|
||||||
if (rowsToDelete.length === 0) {
|
|
||||||
return; // Все строки подходят
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rowsToKeep.length === 0) {
|
|
||||||
// Удаляем все строки источника
|
|
||||||
rowsToDelete.forEach(row => row.remove());
|
|
||||||
} else {
|
|
||||||
// Есть строки для сохранения
|
|
||||||
// Удаляем строки по одной, используя функцию removeObjItem
|
|
||||||
rowsToDelete.forEach(row => {
|
|
||||||
const button = row.querySelector('button[onclick*="removeObjItem"]');
|
|
||||||
if (button) {
|
|
||||||
removeObjItem(button);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
updateCounter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function exportToExcel() {
|
function exportToExcel() {
|
||||||
// Собираем ID оставшихся в таблице точек (ObjItem)
|
// Собираем ID оставшихся в таблице точек (ObjItem)
|
||||||
@@ -544,6 +525,16 @@ function exportToExcel() {
|
|||||||
document.body.removeChild(form);
|
document.body.removeChild(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для выбора/снятия всех опций в select
|
||||||
|
function selectAllOptions(selectName, selectAll) {
|
||||||
|
const selectElement = document.querySelector(`select[name="${selectName}"]`);
|
||||||
|
if (selectElement) {
|
||||||
|
for (let i = 0; i < selectElement.options.length; i++) {
|
||||||
|
selectElement.options[i].selected = selectAll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Обновляем счетчик при загрузке страницы
|
// Обновляем счетчик при загрузке страницы
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
updateCounter();
|
updateCounter();
|
||||||
@@ -563,10 +554,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Выделение строк, подходящих по дате */
|
|
||||||
.table-success {
|
|
||||||
background-color: #d1e7dd !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для кнопок действий */
|
/* Стили для кнопок действий */
|
||||||
.btn-sm {
|
.btn-sm {
|
||||||
|
|||||||
@@ -31,49 +31,58 @@ class KubsatView(LoginRequiredMixin, FormView):
|
|||||||
form = self.form_class(self.request.GET)
|
form = self.form_class(self.request.GET)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
sources = self.apply_filters(form.cleaned_data)
|
sources = self.apply_filters(form.cleaned_data)
|
||||||
|
|
||||||
# Определяем, какие источники подходят по дате
|
|
||||||
date_from = form.cleaned_data.get('date_from')
|
date_from = form.cleaned_data.get('date_from')
|
||||||
date_to = form.cleaned_data.get('date_to')
|
date_to = form.cleaned_data.get('date_to')
|
||||||
|
has_date_filter = bool(date_from or date_to)
|
||||||
|
|
||||||
# Добавляем информацию о соответствии дате для каждого источника
|
objitem_count = form.cleaned_data.get('objitem_count')
|
||||||
sources_with_date_info = []
|
sources_with_date_info = []
|
||||||
for source in sources:
|
for source in sources:
|
||||||
source_data = {
|
source_data = {
|
||||||
'source': source,
|
'source': source,
|
||||||
'matches_date': False,
|
|
||||||
'objitems_data': []
|
'objitems_data': []
|
||||||
}
|
}
|
||||||
|
|
||||||
# Проверяем каждый ObjItem
|
|
||||||
for objitem in source.source_objitems.all():
|
for objitem in source.source_objitems.all():
|
||||||
objitem_matches_date = False
|
objitem_matches_date = True
|
||||||
geo_date = None
|
geo_date = None
|
||||||
|
|
||||||
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.timestamp:
|
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.timestamp:
|
||||||
geo_date = objitem.geo_obj.timestamp.date()
|
geo_date = objitem.geo_obj.timestamp.date()
|
||||||
|
|
||||||
# Проверяем попадание в диапазон дат
|
# Проверяем попадание в диапазон дат (только если фильтр задан)
|
||||||
if date_from and date_to:
|
if has_date_filter:
|
||||||
objitem_matches_date = date_from <= geo_date <= date_to
|
if date_from and date_to:
|
||||||
elif date_from:
|
objitem_matches_date = date_from <= geo_date <= date_to
|
||||||
objitem_matches_date = geo_date >= date_from
|
elif date_from:
|
||||||
elif date_to:
|
objitem_matches_date = geo_date >= date_from
|
||||||
objitem_matches_date = geo_date <= date_to
|
elif date_to:
|
||||||
else:
|
objitem_matches_date = geo_date <= date_to
|
||||||
objitem_matches_date = True # Нет фильтра по дате
|
elif has_date_filter:
|
||||||
|
# Если фильтр по дате задан, но у точки нет даты - не подходит
|
||||||
|
objitem_matches_date = False
|
||||||
|
|
||||||
source_data['objitems_data'].append({
|
# Добавляем только точки, подходящие по дате (или все, если фильтр не задан)
|
||||||
'objitem': objitem,
|
if not has_date_filter or objitem_matches_date:
|
||||||
'matches_date': objitem_matches_date,
|
source_data['objitems_data'].append({
|
||||||
'geo_date': geo_date
|
'objitem': objitem,
|
||||||
})
|
'matches_date': objitem_matches_date,
|
||||||
|
'geo_date': geo_date
|
||||||
|
})
|
||||||
|
|
||||||
# Если хотя бы одна точка подходит по дате, весь источник подходит
|
# ЭТАП 2: Проверяем количество отфильтрованных точек
|
||||||
if objitem_matches_date:
|
filtered_count = len(source_data['objitems_data'])
|
||||||
source_data['matches_date'] = True
|
|
||||||
|
|
||||||
sources_with_date_info.append(source_data)
|
# Применяем фильтр по количеству точек (если задан)
|
||||||
|
include_source = True
|
||||||
|
if objitem_count:
|
||||||
|
if objitem_count == '1':
|
||||||
|
include_source = (filtered_count == 1)
|
||||||
|
elif objitem_count == '2+':
|
||||||
|
include_source = (filtered_count >= 2)
|
||||||
|
|
||||||
|
if source_data['objitems_data'] and include_source:
|
||||||
|
sources_with_date_info.append(source_data)
|
||||||
|
|
||||||
context['sources_with_date_info'] = sources_with_date_info
|
context['sources_with_date_info'] = sources_with_date_info
|
||||||
context['form'] = form
|
context['form'] = form
|
||||||
@@ -95,9 +104,11 @@ class KubsatView(LoginRequiredMixin, FormView):
|
|||||||
source_objitems__parameter_obj__id_satellite__in=filters['satellites']
|
source_objitems__parameter_obj__id_satellite__in=filters['satellites']
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
# Фильтр по полосе спутника (пока не реализован полностью)
|
# Фильтр по полосе спутника
|
||||||
if filters.get('band'):
|
if filters.get('band'):
|
||||||
pass # TODO: реализовать фильтр по band
|
queryset = queryset.filter(
|
||||||
|
source_objitems__parameter_obj__id_satellite__band__in=filters['band']
|
||||||
|
).distinct()
|
||||||
|
|
||||||
# Фильтр по поляризации
|
# Фильтр по поляризации
|
||||||
if filters.get('polarization'):
|
if filters.get('polarization'):
|
||||||
@@ -197,7 +208,7 @@ class KubsatExportView(LoginRequiredMixin, FormView):
|
|||||||
'Обратный канал, МГц',
|
'Обратный канал, МГц',
|
||||||
'Перенос',
|
'Перенос',
|
||||||
'Получено координат, раз',
|
'Получено координат, раз',
|
||||||
'Дата',
|
'Период получения координат',
|
||||||
'Зеркала',
|
'Зеркала',
|
||||||
'СКО, км',
|
'СКО, км',
|
||||||
'Примечание',
|
'Примечание',
|
||||||
@@ -283,21 +294,42 @@ class KubsatExportView(LoginRequiredMixin, FormView):
|
|||||||
mirrors.append(mirror.name)
|
mirrors.append(mirror.name)
|
||||||
mirrors_str = '\n'.join(mirrors)
|
mirrors_str = '\n'.join(mirrors)
|
||||||
|
|
||||||
|
# Диапазон дат ГЛ (самая ранняя - самая поздняя)
|
||||||
|
geo_dates = []
|
||||||
|
for objitem in objitems_list:
|
||||||
|
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.timestamp:
|
||||||
|
geo_dates.append(objitem.geo_obj.timestamp.date())
|
||||||
|
|
||||||
|
date_range_str = '-'
|
||||||
|
if geo_dates:
|
||||||
|
min_date = min(geo_dates)
|
||||||
|
max_date = max(geo_dates)
|
||||||
|
# Форматируем даты в формате d.m.Y
|
||||||
|
min_date_str = min_date.strftime('%d.%m.%Y')
|
||||||
|
max_date_str = max_date.strftime('%d.%m.%Y')
|
||||||
|
|
||||||
|
if min_date == max_date:
|
||||||
|
# Если даты совпадают, показываем только одну
|
||||||
|
date_range_str = min_date_str
|
||||||
|
else:
|
||||||
|
# Иначе показываем диапазон
|
||||||
|
date_range_str = f"{min_date_str}-{max_date_str}"
|
||||||
|
|
||||||
# Записываем строку
|
# Записываем строку
|
||||||
ws.cell(row=row_num, column=1, value=current_date)
|
ws.cell(row=row_num, column=1, value=current_date)
|
||||||
ws.cell(row=row_num, column=2, value=latitude)
|
ws.cell(row=row_num, column=2, value=latitude)
|
||||||
ws.cell(row=row_num, column=3, value=longitude)
|
ws.cell(row=row_num, column=3, value=longitude)
|
||||||
ws.cell(row=row_num, column=4, value=0) # Высота всегда 0
|
ws.cell(row=row_num, column=4, value=0.0)
|
||||||
ws.cell(row=row_num, column=5, value=location)
|
ws.cell(row=row_num, column=5, value=location)
|
||||||
ws.cell(row=row_num, column=6, value=satellite_info)
|
ws.cell(row=row_num, column=6, value=satellite_info)
|
||||||
ws.cell(row=row_num, column=7, value=direct_channel)
|
ws.cell(row=row_num, column=7, value=direct_channel)
|
||||||
ws.cell(row=row_num, column=8, value=reverse_channel)
|
ws.cell(row=row_num, column=8, value=reverse_channel)
|
||||||
ws.cell(row=row_num, column=9, value=transfer)
|
ws.cell(row=row_num, column=9, value=transfer)
|
||||||
ws.cell(row=row_num, column=10, value=objitem_count)
|
ws.cell(row=row_num, column=10, value=objitem_count)
|
||||||
ws.cell(row=row_num, column=11, value='-') # Дата (пока не заполняется)
|
ws.cell(row=row_num, column=11, value=date_range_str)
|
||||||
ws.cell(row=row_num, column=12, value=mirrors_str)
|
ws.cell(row=row_num, column=12, value=mirrors_str)
|
||||||
ws.cell(row=row_num, column=13, value='') # СКО не заполняется
|
ws.cell(row=row_num, column=13, value='')
|
||||||
ws.cell(row=row_num, column=14, value='') # Примечание не заполняется
|
ws.cell(row=row_num, column=14, value='')
|
||||||
ws.cell(row=row_num, column=15, value=operator_name)
|
ws.cell(row=row_num, column=15, value=operator_name)
|
||||||
|
|
||||||
row_num += 1
|
row_num += 1
|
||||||
@@ -325,6 +357,6 @@ class KubsatExportView(LoginRequiredMixin, FormView):
|
|||||||
output.read(),
|
output.read(),
|
||||||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
)
|
)
|
||||||
response['Content-Disposition'] = f'attachment; filename="kubsat_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx"'
|
response['Content-Disposition'] = f'attachment; filename="kubsat_{datetime.now().strftime("%Y%m%d")}.xlsx"'
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
"page_obj": page_obj,
|
"page_obj": page_obj,
|
||||||
"processed_objects": processed_objects,
|
"processed_objects": processed_objects,
|
||||||
"items_per_page": items_per_page,
|
"items_per_page": items_per_page,
|
||||||
"available_items_per_page": [50, 100, 500, 1000],
|
"available_items_per_page": [50, 100, 200, 500, 1000],
|
||||||
"freq_min": freq_min,
|
"freq_min": freq_min,
|
||||||
"freq_max": freq_max,
|
"freq_max": freq_max,
|
||||||
"range_min": range_min,
|
"range_min": range_min,
|
||||||
|
|||||||
Reference in New Issue
Block a user