Аутентификация и кто и когда создал

This commit is contained in:
2025-11-01 10:26:56 +03:00
parent 78c46a2751
commit e01785fa53
44 changed files with 495 additions and 777 deletions

View File

@@ -150,6 +150,11 @@ USE_I18N = True
USE_TZ = True
# Authentication settings
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/

View File

@@ -17,6 +17,7 @@ Including another URLconf
from django.contrib import admin
from django.urls import path, include
from mainapp import views
from django.contrib.auth import views as auth_views
from debug_toolbar.toolbar import debug_toolbar_urls
urlpatterns = [
@@ -24,5 +25,8 @@ urlpatterns = [
path('admin/', admin.site.urls, name='admin'),
# path('admin/map/', views.show_map_view, name='admin_show_map'),
path('', include('mainapp.urls')),
path('', include('mapsapp.urls'))
path('', include('mapsapp.urls')),
# Authentication URLs
path('login/', auth_views.LoginView.as_view(), name='login'),
path('logout/', views.custom_logout, name='logout'),
] + debug_toolbar_urls()

View File

@@ -40,10 +40,17 @@ from .filters import GeoKupDistanceFilter, GeoValidDistanceFilter, UniqueToggleF
admin.site.site_title = "Геолокация"
admin.site.site_header = "Geolocation"
admin.site.index_title = "Geo"
# Unregister default User and Group since we're customizing them
admin.site.unregister(User)
admin.site.unregister(Group)
class CustomUserInline(admin.StackedInline):
model = CustomUser
can_delete = False
verbose_name_plural = 'Дополнительная информация пользователя'
class LocationForm(forms.ModelForm):
latitude_geo = forms.FloatField(required=False, label="Широта")
longitude_geo = forms.FloatField(required=False, label="Долгота")
@@ -117,23 +124,18 @@ class GeoInline(admin.StackedInline):
)
class CustomUserInline(admin.StackedInline):
model = CustomUser
can_delete = False
verbose_name_plural = 'Дополнительная информация пользователя'
@admin.register(CustomUser)
class CustomUserAdmin(admin.ModelAdmin):
list_display = ('user', 'role')
list_filter = ('role',)
class UserAdmin(BaseUserAdmin):
inlines = [CustomUserInline]
admin.site.register(User, UserAdmin)
# @admin.register(CustomUser)
# class CustomUserAdmin(admin.ModelAdmin):
# list_display = ('user', 'role')
# list_filter = ('role',)
# raw_id_fields = ('user',) # For better performance with large number of users
@admin.register(SigmaParMark)
class SigmaParMarkAdmin(admin.ModelAdmin):
list_display = ("mark", "timestamp")
@@ -410,7 +412,6 @@ class ParameterObjItemInline(admin.StackedInline):
max_num = 1
verbose_name = "ВЧ загрузка"
verbose_name_plural = "ВЧ загрузки"
@admin.register(ObjItem)
@@ -430,6 +431,8 @@ class ObjectAdmin(admin.ModelAdmin):
"distance_geo_kup",
"distance_geo_valid",
"distance_kup_valid",
"created_at",
"updated_at",
)
list_display_links = ("name",)
list_filter = (
@@ -452,18 +455,28 @@ class ObjectAdmin(admin.ModelAdmin):
ordering = ("name",)
inlines = [ParameterObjItemInline, GeoInline]
actions = [show_on_map]
readonly_fields = ('created_at', 'created_by', 'updated_at', 'updated_by')
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related('geo_obj').prefetch_related(
return qs.select_related('geo_obj', 'created_by', 'updated_by').prefetch_related(
'parameters_obj__id_satellite',
'parameters_obj__polarization',
'parameters_obj__modulation',
'parameters_obj__standard'
)
def get_readonly_fields(self, request, obj=None):
# Always make these fields readonly to preserve tracking
return self.readonly_fields
def save_model(self, request, obj, form, change):
if not change:
if not obj.created_by_id:
obj.created_by = request.user.customuser if hasattr(request.user, 'customuser') else None
obj.updated_by = request.user.customuser if hasattr(request.user, 'customuser') else None
super().save_model(request, obj, form, change)
def sat_name(self, obj):
param = next(iter(obj.parameters_obj.all()), None)
if param and param.id_satellite:

View File

@@ -4,3 +4,6 @@ from django.apps import AppConfig
class MainappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'mainapp'
def ready(self):
import mainapp.signals # noqa

View File

@@ -0,0 +1,20 @@
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from mainapp.models import CustomUser
class Command(BaseCommand):
help = 'Create CustomUser profiles for existing users who do not have them'
def handle(self, *args, **options):
# Find all users who don't have a CustomUser profile
for user in User.objects.all():
if not hasattr(user, 'customuser'):
custom_user = CustomUser.objects.create(user=user)
self.stdout.write(
self.style.SUCCESS(f'Created CustomUser for {user.username}')
)
self.stdout.write(
self.style.SUCCESS('Successfully ensured all users have CustomUser profiles')
)

View File

@@ -1,7 +1,9 @@
# Generated by Django 5.2.7 on 2025-10-13 12:47
# Generated by Django 5.2.7 on 2025-10-31 13:36
import django.contrib.gis.db.models.fields
import django.contrib.gis.db.models.functions
import django.db.models.deletion
import django.db.models.expressions
import mainapp.models
from django.conf import settings
from django.db import migrations, models
@@ -31,7 +33,7 @@ class Migration(migrations.Migration):
name='Modulation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True, verbose_name='Модуляция')),
('name', models.CharField(db_index=True, max_length=20, unique=True, verbose_name='Модуляция')),
],
options={
'verbose_name': 'Модуляция',
@@ -53,7 +55,7 @@ class Migration(migrations.Migration):
name='Satellite',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=30, unique=True, verbose_name='Имя спутника')),
('name', models.CharField(db_index=True, max_length=100, unique=True, verbose_name='Имя спутника')),
('norad', models.IntegerField(blank=True, null=True, verbose_name='NORAD ID')),
],
options={
@@ -61,6 +63,18 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Спутники',
},
),
migrations.CreateModel(
name='SigmaParMark',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mark', models.BooleanField(blank=True, null=True, verbose_name='Наличие сигнала')),
('timestamp', models.DateTimeField(blank=True, null=True, verbose_name='Время')),
],
options={
'verbose_name': 'Отметка',
'verbose_name_plural': 'Отметки',
},
),
migrations.CreateModel(
name='Standard',
fields=[
@@ -85,35 +99,30 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
name='Geo',
name='ObjItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField(blank=True, null=True, verbose_name='Время')),
('coords', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Координата геолокации')),
('location', models.CharField(blank=True, max_length=255, null=True, verbose_name='Метоположение')),
('comment', models.CharField(blank=True, max_length=255, verbose_name='Комментарий')),
('coords_kupsat', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Координаты Кубсата')),
('coords_valid', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Координаты оперативников')),
('is_average', models.BooleanField(blank=True, null=True, verbose_name='Усреднённое')),
('id_user_add', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='geos_added', to='mainapp.customuser', verbose_name='Пользователь')),
('mirrors', models.ManyToManyField(related_name='geo_mirrors', to='mainapp.mirror', verbose_name='Зеркала')),
('name', models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='Имя объекта')),
('id_user_add', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objitems', to='mainapp.customuser', verbose_name='Пользователь')),
],
options={
'verbose_name': 'Гео',
'verbose_name_plural': 'Гео',
'verbose_name': 'Объект',
'verbose_name_plural': 'Объекты',
},
),
migrations.CreateModel(
name='Parameter',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('frequency', models.FloatField(blank=True, default=0, null=True, verbose_name='Частота, МГц')),
('frequency', models.FloatField(blank=True, db_index=True, default=0, null=True, verbose_name='Частота, МГц')),
('freq_range', models.FloatField(blank=True, default=0, null=True, verbose_name='Полоса частот, МГц')),
('bod_velocity', models.FloatField(blank=True, default=0, null=True, verbose_name='Символьная скорость, БОД')),
('snr', models.FloatField(blank=True, default=0, null=True, verbose_name='ОСШ')),
('id_user_add', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parameter_added', to='mainapp.customuser', verbose_name='Пользователь')),
('modulation', models.ForeignKey(blank=True, default=mainapp.models.get_default_modulation, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='modulations', to='mainapp.modulation', verbose_name='Модуляция')),
('objitems', models.ManyToManyField(blank=True, related_name='parameters_obj', to='mainapp.objitem', verbose_name='Источники')),
('polarization', models.ForeignKey(blank=True, default=mainapp.models.get_default_polarization, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='polarizations', to='mainapp.polarization', verbose_name='Поляризация')),
('id_satellite', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='parameters', to='mainapp.satellite', verbose_name='Спутник')),
('standard', models.ForeignKey(blank=True, default=mainapp.models.get_default_standard, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='standards', to='mainapp.standard', verbose_name='Стандарт')),
],
options={
@@ -122,26 +131,74 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
name='ObjItem',
name='SourceType',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Имя объекта')),
('id_geo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='objitems', to='mainapp.geo', verbose_name='Геоданные')),
('id_user_add', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objitems', to='mainapp.customuser', verbose_name='Пользователь')),
('id_vch_load', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='objitems', to='mainapp.parameter', verbose_name='ВЧ загрузка')),
('id_satellite', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='objitems', to='mainapp.satellite', verbose_name='Спутник')),
('name', models.CharField(max_length=50, unique=True, verbose_name='Тип источника')),
('objitem', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='source_type_obj', to='mainapp.objitem', verbose_name='Гео')),
],
options={
'verbose_name': 'Объект',
'verbose_name_plural': 'Объекты',
'verbose_name': 'Тип источника',
'verbose_name_plural': 'Типы источников',
},
),
migrations.AddConstraint(
model_name='geo',
constraint=models.UniqueConstraint(fields=('timestamp', 'coords'), name='unique_geo_combination'),
migrations.CreateModel(
name='SigmaParameter',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('transfer', models.FloatField(choices=[(-1.0, '-'), (9750.0, '9750 МГц'), (10750.0, '10750 МГц')], default=-1.0, verbose_name='Перенос по частоте')),
('status', models.CharField(blank=True, max_length=20, null=True, verbose_name='Статус')),
('frequency', models.FloatField(blank=True, db_index=True, default=0, null=True, verbose_name='Частота, МГц')),
('transfer_frequency', models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.expressions.CombinedExpression(models.F('frequency'), '+', models.F('transfer')), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Частота в Ku, МГц')),
('freq_range', models.FloatField(blank=True, default=0, null=True, verbose_name='Полоса частот, МГц')),
('power', models.FloatField(blank=True, default=0, null=True, verbose_name='Мощность, дБм')),
('bod_velocity', models.FloatField(blank=True, default=0, null=True, verbose_name='Символьная скорость, БОД')),
('snr', models.FloatField(blank=True, default=0, null=True, verbose_name='ОСШ, Дб')),
('packets', models.BooleanField(blank=True, null=True, verbose_name='Пакетность')),
('datetime_begin', models.DateTimeField(blank=True, null=True, verbose_name='Время начала измерения')),
('datetime_end', models.DateTimeField(blank=True, null=True, verbose_name='Время окончания измерения')),
('id_satellite', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sigmapar_sat', to='mainapp.satellite', verbose_name='Спутник')),
('modulation', models.ForeignKey(blank=True, default=mainapp.models.get_default_modulation, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='modulations_sigma', to='mainapp.modulation', verbose_name='Модуляция')),
('parameter', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sigma_parameter', to='mainapp.parameter', verbose_name='ВЧ')),
('polarization', models.ForeignKey(blank=True, default=mainapp.models.get_default_polarization, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='polarizations_sigma', to='mainapp.polarization', verbose_name='Поляризация')),
('mark', models.ManyToManyField(blank=True, to='mainapp.sigmaparmark', verbose_name='Отметка')),
('standard', models.ForeignKey(blank=True, default=mainapp.models.get_default_standard, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='standards_sigma', to='mainapp.standard', verbose_name='Стандарт')),
],
options={
'verbose_name': 'ВЧ sigma',
'verbose_name_plural': 'ВЧ sigma',
},
),
migrations.AddConstraint(
model_name='objitem',
constraint=models.UniqueConstraint(fields=('id_vch_load', 'id_geo'), name='unique_objitem_combination'),
migrations.CreateModel(
name='Geo',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Время')),
('coords', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Координата геолокации')),
('location', models.CharField(blank=True, max_length=255, null=True, verbose_name='Метоположение')),
('comment', models.CharField(blank=True, max_length=255, verbose_name='Комментарий')),
('coords_kupsat', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Координаты Кубсата')),
('coords_valid', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Координаты оперативников')),
('is_average', models.BooleanField(blank=True, null=True, verbose_name='Усреднённое')),
('distance_coords_kup', models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords', 'coords_kupsat'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и гео, км')),
('distance_coords_valid', models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords', 'coords_valid'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между гео и оперативным отделом, км')),
('distance_kup_valid', models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords_valid', 'coords_kupsat'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и оперативным отделом, км')),
('id_user_add', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='geos_added', to='mainapp.customuser', verbose_name='Пользователь')),
('mirrors', models.ManyToManyField(related_name='geo_mirrors', to='mainapp.mirror', verbose_name='Зеркала')),
('objitem', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='geo_obj', to='mainapp.objitem', verbose_name='Гео')),
],
options={
'verbose_name': 'Гео',
'verbose_name_plural': 'Гео',
'constraints': [models.UniqueConstraint(fields=('timestamp', 'coords'), name='unique_geo_combination')],
},
),
migrations.AddIndex(
model_name='parameter',
index=models.Index(fields=['id_satellite', 'frequency'], name='mainapp_par_id_sate_cbfab2_idx'),
),
migrations.AddIndex(
model_name='parameter',
index=models.Index(fields=['frequency', 'polarization'], name='mainapp_par_frequen_75a049_idx'),
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-15 09:23
import django.contrib.gis.db.models.functions
import django.db.models.expressions
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='geo',
name='distance_coords_kup',
field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords', 'coords_kupsat'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и гео'),
),
]

View File

@@ -0,0 +1,35 @@
# Generated by Django 5.2.7 on 2025-10-31 13:56
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='objitem',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Дата создания'),
),
migrations.AddField(
model_name='objitem',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objitems_created', to='mainapp.customuser', verbose_name='Создан пользователем'),
),
migrations.AddField(
model_name='objitem',
name='updated_at',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Дата последнего изменения'),
),
migrations.AddField(
model_name='objitem',
name='updated_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objitems_updated', to='mainapp.customuser', verbose_name='Изменен пользователем'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.7 on 2025-10-31 14:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0002_objitem_created_at_objitem_created_by_and_more'),
]
operations = [
migrations.AlterField(
model_name='objitem',
name='created_at',
field=models.DateTimeField(auto_now_add=True, verbose_name='Дата создания'),
),
migrations.AlterField(
model_name='objitem',
name='updated_at',
field=models.DateTimeField(auto_now=True, verbose_name='Дата последнего изменения'),
),
]

View File

@@ -1,30 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-15 09:43
import django.contrib.gis.db.models.functions
import django.db.models.expressions
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0002_geo_distance_coords_kup'),
]
operations = [
migrations.AddField(
model_name='geo',
name='distance_coords_valid',
field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords', 'coords_valid'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между гео и оперативным отделом, км'),
),
migrations.AddField(
model_name='geo',
name='distance_kup_valid',
field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords_valid', 'coords_kupsat'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и оперативным отделом, км'),
),
migrations.AlterField(
model_name='geo',
name='distance_coords_kup',
field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords', 'coords_kupsat'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и гео, км'),
),
]

View File

@@ -1,36 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-16 12:50
import django.db.models.deletion
import mainapp.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0003_geo_distance_coords_valid_geo_distance_kup_valid_and_more'),
]
operations = [
migrations.CreateModel(
name='SigmaParameter',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(blank=True, max_length=20, null=True, verbose_name='Статус')),
('frequency', models.FloatField(blank=True, default=0, null=True, verbose_name='Частота, МГц')),
('freq_range', models.FloatField(blank=True, default=0, null=True, verbose_name='Полоса частот, МГц')),
('power', models.FloatField(blank=True, default=0, null=True, verbose_name='Мощность, дБм')),
('bod_velocity', models.FloatField(blank=True, default=0, null=True, verbose_name='Символьная скорость, БОД')),
('snr', models.FloatField(blank=True, default=0, null=True, verbose_name='ОСШ, Дб')),
('packets', models.BooleanField(blank=True, null=True, verbose_name='Пакетность')),
('datetime_begin', models.DateTimeField(blank=True, null=True, verbose_name='Время начала измерения')),
('datetime_end', models.DateTimeField(blank=True, null=True, verbose_name='Время окончания измерения')),
('modulation', models.ForeignKey(blank=True, default=mainapp.models.get_default_modulation, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='modulations_sigma', to='mainapp.modulation', verbose_name='Модуляция')),
('standard', models.ForeignKey(blank=True, default=mainapp.models.get_default_standard, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='standards_sigma', to='mainapp.standard', verbose_name='Стандарт')),
],
options={
'verbose_name': 'ВЧ sigma',
'verbose_name_plural': 'ВЧ sigma',
},
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-20 07:57
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0004_sigmaparameter'),
]
operations = [
migrations.AddField(
model_name='sigmaparameter',
name='id_satellite',
field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.PROTECT, related_name='sigmapar_sat', to='mainapp.satellite', verbose_name='Спутник'),
preserve_default=False,
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-20 11:57
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0005_sigmaparameter_id_satellite'),
]
operations = [
migrations.RemoveField(
model_name='objitem',
name='id_satellite',
),
migrations.AddField(
model_name='parameter',
name='id_satellite',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='parameters', to='mainapp.satellite', verbose_name='Спутник'),
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-20 11:58
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0006_remove_objitem_id_satellite_parameter_id_satellite'),
]
operations = [
migrations.AlterField(
model_name='parameter',
name='id_satellite',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='parameters', to='mainapp.satellite', verbose_name='Спутник'),
),
]

View File

@@ -1,38 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-22 11:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0007_alter_parameter_id_satellite'),
]
operations = [
migrations.AlterField(
model_name='geo',
name='timestamp',
field=models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Время'),
),
migrations.AlterField(
model_name='objitem',
name='name',
field=models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='Имя объекта'),
),
migrations.AlterField(
model_name='parameter',
name='frequency',
field=models.FloatField(blank=True, db_index=True, default=0, null=True, verbose_name='Частота, МГц'),
),
migrations.AlterField(
model_name='satellite',
name='name',
field=models.CharField(db_index=True, max_length=30, unique=True, verbose_name='Имя спутника'),
),
migrations.AlterField(
model_name='sigmaparameter',
name='frequency',
field=models.FloatField(blank=True, db_index=True, default=0, null=True, verbose_name='Частота, МГц'),
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-22 12:08
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0008_alter_geo_timestamp_alter_objitem_name_and_more'),
]
operations = [
migrations.AddField(
model_name='parameter',
name='id_sigma_parameter',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sigma_parameter', to='mainapp.sigmaparameter', verbose_name='ВЧ с sigma'),
),
]

View File

@@ -1,31 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-22 12:38
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0009_parameter_id_sigma_parameter'),
]
operations = [
migrations.CreateModel(
name='SigmaParMark',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mark', models.BooleanField(blank=True, null=True, verbose_name='Наличие сигнала')),
('timestamp', models.DateTimeField(blank=True, null=True, verbose_name='Время')),
],
options={
'verbose_name': 'Отметка',
'verbose_name_plural': 'Отметки',
},
),
migrations.AddField(
model_name='sigmaparameter',
name='mark',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mainapp.sigmaparmark', verbose_name='Отметка'),
),
]

View File

@@ -1,22 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-22 13:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0010_sigmaparmark_sigmaparameter_mark'),
]
operations = [
migrations.RemoveField(
model_name='sigmaparameter',
name='mark',
),
migrations.AddField(
model_name='sigmaparameter',
name='mark',
field=models.ManyToManyField(blank=True, null=True, to='mainapp.sigmaparmark', verbose_name='Отметка'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-22 13:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0011_remove_sigmaparameter_mark_sigmaparameter_mark'),
]
operations = [
migrations.AlterField(
model_name='sigmaparameter',
name='mark',
field=models.ManyToManyField(blank=True, to='mainapp.sigmaparmark', verbose_name='Отметка'),
),
]

View File

@@ -1,21 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-22 13:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0012_alter_sigmaparameter_mark'),
]
operations = [
migrations.AddIndex(
model_name='parameter',
index=models.Index(fields=['id_satellite', 'frequency'], name='mainapp_par_id_sate_cbfab2_idx'),
),
migrations.AddIndex(
model_name='parameter',
index=models.Index(fields=['frequency', 'polarization'], name='mainapp_par_frequen_75a049_idx'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-23 08:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0013_parameter_mainapp_par_id_sate_cbfab2_idx_and_more'),
]
operations = [
migrations.AlterField(
model_name='modulation',
name='name',
field=models.CharField(db_index=True, max_length=20, unique=True, verbose_name='Модуляция'),
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-23 09:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0014_alter_modulation_name'),
]
operations = [
migrations.AlterField(
model_name='parameter',
name='id_sigma_parameter',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sigma_parameter', to='mainapp.sigmaparameter', verbose_name='ВЧ с sigma'),
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-23 09:58
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0015_alter_parameter_id_sigma_parameter'),
]
operations = [
migrations.RemoveField(
model_name='parameter',
name='id_sigma_parameter',
),
migrations.AddField(
model_name='sigmaparameter',
name='parameter',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sigma_parameter', to='mainapp.parameter', verbose_name='ВЧ с sigma'),
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-23 12:52
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0016_remove_parameter_id_sigma_parameter_and_more'),
]
operations = [
migrations.AlterField(
model_name='sigmaparameter',
name='parameter',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sigma_parameter', to='mainapp.parameter', verbose_name='ВЧ'),
),
]

View File

@@ -1,31 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-27 13:10
import django.db.models.deletion
import django.db.models.expressions
import mainapp.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0017_alter_sigmaparameter_parameter'),
]
operations = [
migrations.AddField(
model_name='sigmaparameter',
name='polarization',
field=models.ForeignKey(blank=True, default=mainapp.models.get_default_polarization, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='polarizations_sigma', to='mainapp.polarization', verbose_name='Поляризация'),
),
migrations.AddField(
model_name='sigmaparameter',
name='transfer',
field=models.FloatField(choices=[(-1.0, '-'), (9750.0, '9750 МГц'), (10750.0, '10750 МГц')], default=-1.0, verbose_name='Перенос по частоте'),
),
migrations.AddField(
model_name='sigmaparameter',
name='transfer_frequency',
field=models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.expressions.CombinedExpression(models.F('frequency'), '+', models.F('transfer')), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Частота в Ku, МГц'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-28 05:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0018_sigmaparameter_polarization_sigmaparameter_transfer_and_more'),
]
operations = [
migrations.AlterField(
model_name='satellite',
name='name',
field=models.CharField(db_index=True, max_length=100, unique=True, verbose_name='Имя спутника'),
),
]

View File

@@ -1,30 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-29 14:00
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0019_alter_satellite_name'),
]
operations = [
migrations.CreateModel(
name='SourceType',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, unique=True, verbose_name='Тип источника')),
],
options={
'verbose_name': 'Тип источника',
'verbose_name_plural': 'Типы источников',
},
),
migrations.AddField(
model_name='objitem',
name='id_source_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objitems', to='mainapp.sourcetype', verbose_name='Тип источника'),
),
]

View File

@@ -1,45 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-30 06:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0020_sourcetype_objitem_id_source_type'),
]
operations = [
migrations.RemoveConstraint(
model_name='objitem',
name='unique_objitem_combination',
),
migrations.RemoveField(
model_name='objitem',
name='id_geo',
),
migrations.RemoveField(
model_name='objitem',
name='id_source_type',
),
migrations.RemoveField(
model_name='objitem',
name='id_vch_load',
),
migrations.AddField(
model_name='geo',
name='objitem',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='geo_obj', to='mainapp.objitem', verbose_name='Гео'),
),
migrations.AddField(
model_name='parameter',
name='objitem',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parameters_obj', to='mainapp.objitem', verbose_name='Источник'),
),
migrations.AddField(
model_name='sourcetype',
name='objitem',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='source_type_obj', to='mainapp.objitem', verbose_name='Гео'),
),
]

View File

@@ -1,22 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-30 07:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0021_remove_objitem_unique_objitem_combination_and_more'),
]
operations = [
migrations.RemoveField(
model_name='parameter',
name='objitem',
),
migrations.AddField(
model_name='parameter',
name='objitems',
field=models.ManyToManyField(blank=True, related_name='parameters_obj', to='mainapp.objitem', verbose_name='Источники'),
),
]

View File

@@ -3,6 +3,7 @@ from django.contrib.auth.models import User
from django.contrib.gis.db import models as gis
from django.contrib.gis.db.models import functions
from django.db.models import F, ExpressionWrapper
from django.utils import timezone
def get_default_polarization():
obj, created = Polarization.objects.get_or_create(
@@ -113,8 +114,15 @@ class ObjItem(models.Model):
# id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="objitems", verbose_name="Спутник")
# id_vch_load = models.ForeignKey(Parameter, on_delete=models.CASCADE, related_name="objitems", verbose_name="ВЧ загрузка")
# id_geo = models.ForeignKey(Geo, on_delete=models.CASCADE, related_name="objitems", verbose_name="Геоданные")
id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Пользователь", null=True, blank=True)
# id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Пользователь", null=True, blank=True)
# id_source_type = models.ForeignKey(SourceType, on_delete=models.SET_NULL, related_name="objitems", verbose_name='Тип источника', null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
created_by = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems_created",
null=True, blank=True, verbose_name="Создан пользователем")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Дата последнего изменения")
updated_by = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems_updated",
null=True, blank=True, verbose_name="Изменен пользователем")
def __str__(self):

11
dbapp/mainapp/signals.py Normal file
View File

@@ -0,0 +1,11 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import CustomUser
@receiver(post_save, sender=User)
def create_or_update_user_profile(sender, instance, created, **kwargs):
if created:
CustomUser.objects.create(user=instance)
instance.customuser.save()

View File

@@ -22,6 +22,7 @@
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
{% if user.is_authenticated %}
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'home' %}">Объекты</a>
@@ -39,6 +40,29 @@
<a class="nav-link" href="{% url 'admin:index' %}">Админ панель</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown">
{% if user.first_name and user.last_name %}
{{ user.first_name }} {{ user.last_name }}
{% elif user.get_full_name %}
{{ user.get_full_name }}
{% else %}
{{ user.username }}
{% endif %}
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="{% url 'logout' %}">Выйти</a></li>
</ul>
</li>
</ul>
{% else %}
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'login' %}">Войти</a>
</li>
</ul>
{% endif %}
</div>
</div>
</nav>

View File

@@ -0,0 +1,19 @@
{% extends 'mainapp/base.html' %}
{% block title %}Войдите в систему{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body text-center">
<h2 class="card-title">Требуется авторизация</h2>
<p class="card-text">Для просмотра содержимого сайта необходимо войти в систему.</p>
<a href="{% url 'login' %}" class="btn btn-primary">Войти</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -16,13 +16,84 @@
<div class="card">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center gap-3">
<div style="min-width: 300px; flex-grow: 1;">
<label for="toolbar-search" class="form-label mb-0">Поиск:</label>
<input type="text" id="toolbar-search" class="form-control" placeholder="Поиск по имени, местоположению..." value="{{ search_query|default:'' }}">
<!-- Search bar made more compact -->
<div style="min-width: 200px; flex-grow: 1; max-width: 400px;">
<div class="input-group">
<input type="text" id="toolbar-search" class="form-control" placeholder="Поиск..." value="{{ search_query|default:'' }}">
<button type="button" class="btn btn-outline-primary" onclick="performSearch()">Найти</button>
<button type="button" class="btn btn-outline-secondary" onclick="clearSearch()">Очистить</button>
</div>
</div>
<!-- Action buttons bar -->
<div class="d-flex gap-2">
<button type="button" class="btn btn-success btn-sm" title="Добавить">
<i class="bi bi-plus-circle"></i> Добавить
</button>
<button type="button" class="btn btn-info btn-sm" title="Изменить">
<i class="bi bi-pencil"></i> Изменить
</button>
<button type="button" class="btn btn-danger btn-sm" title="Удалить">
<i class="bi bi-trash"></i> Удалить
</button>
</div>
<!-- Items per page select moved here -->
<div>
<label for="items-per-page" class="form-label mb-0">Показать:</label>
<select name="items_per_page" id="items-per-page" class="form-select form-select-sm d-inline-block" style="width: auto;" onchange="updateItemsPerPage()">
{% for option in available_items_per_page %}
<option value="{{ option }}" {% if option == items_per_page %}selected{% endif %}>
{{ option }}
</option>
{% endfor %}
</select>
</div>
<!-- Pagination moved here -->
<div class="ms-auto">
<button type="button" class="btn btn-outline-primary" onclick="performSearch()">Найти</button>
<button type="button" class="btn btn-outline-secondary" onclick="clearSearch()">Очистить</button>
{% if page_obj.paginator.num_pages > 1 %}
<nav aria-label="Page navigation" class="d-flex align-items-center">
<ul class="pagination mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1" title="Первая"><<</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}" title="Предыдущая"><</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}" title="Следующая">></a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}" title="Последняя">>></a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
<!-- Pagination Info -->
{% if page_obj %}
<div class="ms-3 text-muted small">
{{ page_obj.start_index }}-{{ page_obj.end_index }} из {{ page_obj.paginator.count }}
</div>
{% endif %}
</div>
</div>
</div>
@@ -32,7 +103,7 @@
<div class="row g-3">
<!-- Filters Sidebar - Made narrower -->
<div class="col-md-2">
<div class="col-md-auto">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Фильтры</h5>
@@ -152,18 +223,6 @@
</div>
</div>
<!-- Items Per Page -->
<div class="mb-2">
<label for="items-per-page" class="form-label">Элементов:</label>
<select name="items_per_page" id="items-per-page" class="form-select form-select-sm" onchange="document.getElementById('filter-form').submit();">
{% for option in available_items_per_page %}
<option value="{{ option }}" {% if option == items_per_page %}selected{% endif %}>
{{ option }}
</option>
{% endfor %}
</select>
</div>
<!-- Apply Filters and Reset Buttons -->
<div class="d-grid gap-2 mt-2">
<button type="submit" class="btn btn-primary btn-sm">Применить</button>
@@ -175,7 +234,7 @@
</div>
<!-- Main Table -->
<div class="col-md-10">
<div class="col-md">
<div class="card h-100">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto;">
@@ -189,8 +248,8 @@
<th scope="col">Спутник</th>
<th scope="col">Част, МГц</th>
<th scope="col">Полоса, МГц</th>
<th scope="col">Поляр</th>
<th scope="col">Сим. v</th>
<th scope="col">Поляризация</th>
<th scope="col">Сим. V</th>
<th scope="col">Модул</th>
<th scope="col">ОСШ</th>
<th scope="col">Геолокация</th>
@@ -199,6 +258,8 @@
<th scope="col">Гео-куб, км</th>
<th scope="col">Гео-опер, км</th>
<th scope="col">Куб-опер, км</th>
<th scope="col">Обновлено</th>
<th scope="col">Кем</th>
</tr>
</thead>
<tbody>
@@ -221,10 +282,12 @@
<td>{{ item.distance_geo_kup }}</td>
<td>{{ item.distance_geo_valid }}</td>
<td>{{ item.distance_kup_valid }}</td>
<td>{{ item.obj.updated_at|date:"d.m.Y H:i" }}</td>
<td>{{ item.obj.updated_by }}</td>
</tr>
{% empty %}
<tr>
<td colspan="15" class="text-center py-4">
<td colspan="17" class="text-center py-4">
{% if selected_satellite_id %}
Нет данных для выбранных фильтров
{% else %}
@@ -236,50 +299,6 @@
</tbody>
</table>
</div>
<!-- Pagination -->
{% if page_obj.paginator.num_pages > 1 %}
<nav aria-label="Page navigation" class="px-3 pb-3">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1">Первая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">Предыдущая</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">Следующая</a>
</li>
<li class="page-item">
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}">Последняя</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
<!-- Pagination Info -->
{% if page_obj %}
<div class="px-3 pb-3 d-flex justify-content-between align-items-center">
<div>Показано {{ page_obj.start_index }}-{{ page_obj.end_index }} из {{ page_obj.paginator.count }} записей</div>
</div>
{% endif %}
</div>
</div>
</div>
@@ -417,6 +436,21 @@ document.addEventListener('DOMContentLoaded', function() {
updateSatelliteSelection();
});
}
// Function to update items per page
window.updateItemsPerPage = function() {
const itemsPerPageSelect = document.getElementById('items-per-page');
const currentParams = new URLSearchParams(window.location.search);
// Add or update the items_per_page parameter
currentParams.set('items_per_page', itemsPerPageSelect.value);
// Remove page parameter to reset to first page when changing items per page
currentParams.delete('page');
// Update URL and reload
window.location.search = currentParams.toString();
};
});
</script>
{% endblock %}

View File

@@ -0,0 +1,19 @@
{% extends 'mainapp/base.html' %}
{% block title %}Выход{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body text-center">
<h2 class="card-title">Вы вышли из системы</h2>
<p class="card-text">Вы успешно вышли из системы.</p>
<a href="{% url 'login' %}" class="btn btn-primary">Войти снова</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends 'mainapp/base.html' %}
{% block title %}Вход в систему{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h2 class="card-title text-center">Вход в систему</h2>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="{{ form.username.id_for_label }}" class="form-label">Имя пользователя</label>
{{ form.username }}
</div>
<div class="mb-3">
<label for="{{ form.password.id_for_label }}" class="form-label">Пароль</label>
{{ form.password }}
</div>
{% if form.errors %}
<div class="alert alert-danger">
{% for field in form %}
{% for error in field.errors %}
<p>{{ error }}</p>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<div class="d-grid">
<button type="submit" class="btn btn-primary">Войти</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -5,8 +5,9 @@ from . import views
urlpatterns = [
path('', views.ObjItemListView.as_view(), name='home'), # Make objitems the main page
path('actions/', views.HomePageView.as_view(), name='actions'), # Move actions to a separate page
path('', views.HomePageView.as_view(), name='home'), # Home page that redirects based on auth
path('objitems/', views.ObjItemListView.as_view(), name='objitem_list'), # Objects list page
path('actions/', views.ActionsPageView.as_view(), name='actions'), # Move actions to a separate page
path('excel-data', views.LoadExcelDataView.as_view(), name='load_excel_data'),
path('satellites', views.AddSatellitesView.as_view(), name='add_sats'),
path('api/locations/<int:sat_id>/geojson/', views.GetLocationsView.as_view(), name='locations_by_id'),
@@ -17,7 +18,6 @@ urlpatterns = [
path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'),
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'),
path('objitems/', views.ObjItemListView.as_view(), name='objitem_list'),
# path('upload/', views.upload_file, name='upload_file'),
]

View File

@@ -120,7 +120,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
bod_velocity=v,
modulation=mod_obj,
snr=snr,
defaults={'id_user_add': CustomUser.objects.get(id=1)}
# defaults={'id_user_add': CustomUser.objects.get(id=1)}
)
geo, _ = Geo.objects.get_or_create(
@@ -131,8 +131,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
'coords_valid': valid_point,
'location': location,
'comment': comment,
'is_average': (comment != -1.0),
'id_user_add': CustomUser.objects.get(id=1)
'is_average': (comment != -1.0)
}
)
geo.save()
@@ -150,23 +149,6 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
obj_item.parameters_obj.set([vch_load_obj])
geo.objitem = obj_item
geo.save()
# else:
# obj_item, _ = ObjItem.objects.get_or_create(
# defaults={
# 'name': source,
# 'id_user_add': CustomUser.objects.get(id=1),
# # 'id_satellite': sat
# }
# )
# obj_item.save()
# obj_item.parameters_obj.set([vch_load_obj])
# if geo:
# obj_item.geo_obj = geo
# # или в зависимости от вашей модели Geo, вы можете установить обратную связь там:
# # geo.objitem = obj_item
# geo.save()
@@ -262,7 +244,7 @@ def get_points_from_csv(file_content):
polarization=pol_obj,
frequency=row['freq'],
freq_range=row['f_range'],
defaults={'id_user_add': CustomUser.objects.get(id=1)}
# defaults={'id_user_add': CustomUser.objects.get(id=1)}
)
geo_obj, _ = Geo.objects.get_or_create(
@@ -270,7 +252,7 @@ def get_points_from_csv(file_content):
coords=Point(row['lon'], row['lat'], srid=4326),
defaults={
'is_average': False,
'id_user_add': CustomUser.objects.get(id=1),
# 'id_user_add': CustomUser.objects.get(id=1),
}
)
geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
@@ -282,7 +264,7 @@ def get_points_from_csv(file_content):
if not existing_obj_items.exists():
obj_item = ObjItem.objects.create(
name=row['obj'],
id_user_add=CustomUser.objects.get(id=1)
# id_user_add=CustomUser.objects.get(id=1)
)
obj_item.parameters_obj.set([vch_load_obj])
geo_obj.objitem = obj_item

View File

@@ -3,10 +3,12 @@ from django.contrib import messages
from django.http import JsonResponse, HttpResponse
from django.views.decorators.http import require_GET
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views import View
from django.views.generic import TemplateView, FormView
from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.contrib.auth import logout
from django.db import models
import pandas as pd
from .utils import (
@@ -25,7 +27,7 @@ from io import BytesIO
class AddSatellitesView(View):
class AddSatellitesView(LoginRequiredMixin, View):
def get(self, request):
add_satellite_list()
return redirect('home')
@@ -38,7 +40,7 @@ class AddSatellitesView(View):
# print("Файл не найден")
# return redirect('home')
class AddTranspondersView(FormView):
class AddTranspondersView(LoginRequiredMixin, FormView):
template_name = 'mainapp/transponders_upload.html'
form_class = UploadFileForm
@@ -58,11 +60,26 @@ class AddTranspondersView(FormView):
messages.error(self.request, "Форма заполнена некорректно.")
return super().form_invalid(form)
class HomePageView(TemplateView):
template_name = 'mainapp/actions.html'
from django.views.generic import View
class ActionsPageView(View):
def get(self, request):
if request.user.is_authenticated:
return render(request, 'mainapp/actions.html')
else:
return render(request, 'mainapp/login_required.html')
class LoadExcelDataView(FormView):
class HomePageView(View):
def get(self, request):
if request.user.is_authenticated:
# Redirect to objitem list if authenticated
return redirect('objitem_list')
else:
return render(request, 'mainapp/login_required.html')
class LoadExcelDataView(LoginRequiredMixin, FormView):
template_name = 'mainapp/add_data_from_excel.html'
form_class = LoadExcelData
@@ -94,7 +111,7 @@ from django.core.paginator import Paginator
from django.db.models import Prefetch
from .models import Satellite, ObjItem, Parameter, Geo
class GetLocationsView(View):
class GetLocationsView(LoginRequiredMixin, View):
def get(self, request, sat_id):
locations = ObjItem.objects.filter(parameters_obj__id_satellite=sat_id)
if not locations:
@@ -122,7 +139,7 @@ class GetLocationsView(View):
"features": features
})
class LoadCsvDataView(FormView):
class LoadCsvDataView(LoginRequiredMixin, FormView):
template_name = 'mainapp/add_data_from_csv.html'
form_class = LoadCsvData
@@ -196,17 +213,23 @@ class ShowMapView(UserPassesTestMixin, View):
return render(request, 'admin/map_custom.html', context)
class ClusterTestView(View):
class ClusterTestView(LoginRequiredMixin, View):
def get(self, request):
objs = ObjItem.objects.filter(name__icontains="! Astra 4A 12654,040 [1,962] МГц H")
coords = []
for obj in objs:
coords.append((obj.id_geo.coords[1], obj.id_geo.coords[0]))
if 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": "ок"})
class UploadVchLoadView(FormView):
def custom_logout(request):
logout(request)
return redirect('home')
class UploadVchLoadView(LoginRequiredMixin, FormView):
template_name = 'mainapp/upload_html.html'
form_class = UploadVchLoad
@@ -227,7 +250,7 @@ class UploadVchLoadView(FormView):
return super().form_invalid(form)
class LinkVchSigmaView(FormView):
class LinkVchSigmaView(LoginRequiredMixin, FormView):
template_name = 'mainapp/link_vch.html'
form_class = VchLinkForm
@@ -245,7 +268,7 @@ class LinkVchSigmaView(FormView):
return self.render_to_response(self.get_context_data(form=form))
class ProcessKubsatView(FormView):
class ProcessKubsatView(LoginRequiredMixin, FormView):
template_name = 'mainapp/process_kubsat.html'
form_class = NewEventForm
@@ -278,15 +301,16 @@ class ProcessKubsatView(FormView):
messages.error(self.request, "Форма заполнена некорректно.")
return super().form_invalid(form)
class ObjItemListView(View):
from django.contrib.auth.mixins import LoginRequiredMixin
class ObjItemListView(LoginRequiredMixin, View):
def get(self, request):
# Get satellites that have associated objects, sorted alphabetically
satellites = Satellite.objects.filter(parameters__objitems__isnull=False).distinct().order_by('name')
# Get selected satellite from query parameters
selected_sat_id = request.GET.get('satellite_id')
page_number = request.GET.get('page', 1)
items_per_page = request.GET.get('items_per_page', '25') # Default to 25 items per page
items_per_page = request.GET.get('items_per_page', '50')
# Get filter parameters
freq_min = request.GET.get('freq_min')
@@ -327,7 +351,8 @@ class ObjItemListView(View):
# Start with the basic filter
objects = ObjItem.objects.select_related(
'id_user_add__user',
'geo_obj'
'geo_obj',
'updated_by__user'
).prefetch_related(
'parameters_obj__id_satellite',
'parameters_obj__polarization',
@@ -338,7 +363,8 @@ class ObjItemListView(View):
# If no satellites are selected, start with all objects
objects = ObjItem.objects.select_related(
'id_user_add__user',
'geo_obj'
'geo_obj',
'updated_by__user'
).prefetch_related(
'parameters_obj__id_satellite',
'parameters_obj__polarization',
@@ -499,9 +525,9 @@ class ObjItemListView(View):
'frequency': f"{param.frequency:.3f}" if param and param.frequency else "-",
'freq_range': f"{param.freq_range:.3f}" if param and param.freq_range else "-",
'polarization': param.polarization.name if param and param.polarization else "-",
'bod_velocity': f"{param.bod_velocity:.3f}" if param and param.bod_velocity else "-",
'bod_velocity': f"{param.bod_velocity:.0f}" if param and param.bod_velocity else "-",
'modulation': param.modulation.name if param and param.modulation else "-",
'snr': f"{param.snr:.3f}" if param and param.snr else "-",
'snr': f"{param.snr:.0f}" if param and param.snr else "-",
'geo_coords': geo_coords,
'kupsat_coords': kupsat_coords,
'valid_coords': valid_coords,
@@ -522,7 +548,7 @@ class ObjItemListView(View):
'page_obj': page_obj,
'processed_objects': processed_objects,
'items_per_page': items_per_page,
'available_items_per_page': [10, 25, 50, 100],
'available_items_per_page': [50, 100, 500, 1000],
# Filter values
'freq_min': freq_min,
'freq_max': freq_max,

View File

@@ -1,6 +1,8 @@
# Generated by Django 5.2.7 on 2025-10-13 12:47
# Generated by Django 5.2.7 on 2025-10-31 13:36
import django.db.models.deletion
import django.db.models.expressions
import django.db.models.functions.math
import mainapp.models
from django.db import migrations, models
@@ -19,9 +21,11 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=30, null=True, verbose_name='Название транспондера')),
('frequency', models.FloatField(blank=True, null=True, verbose_name='Центральная частота')),
('frequency_range', models.FloatField(blank=True, null=True, verbose_name='Полоса частот')),
('zone_name', models.CharField(blank=True, max_length=60, null=True, verbose_name='Название зоны')),
('downlink', models.FloatField(blank=True, null=True, verbose_name='Downlink')),
('frequency_range', models.FloatField(blank=True, null=True, verbose_name='Полоса')),
('uplink', models.FloatField(blank=True, null=True, verbose_name='Uplink')),
('zone_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Название зоны')),
('transfer', models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.functions.math.Abs(django.db.models.expressions.CombinedExpression(models.F('downlink'), '-', models.F('uplink'))), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Перенос')),
('polarization', models.ForeignKey(blank=True, default=mainapp.models.get_default_polarization, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='tran_polarizations', to='mainapp.polarization', verbose_name='Поляризация')),
('sat_id', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='tran_satellite', to='mainapp.satellite', verbose_name='Спутник')),
],

View File

@@ -1,34 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-27 12:20
import django.db.models.expressions
import django.db.models.functions.math
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mapsapp', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='transponders',
name='frequency',
),
migrations.AddField(
model_name='transponders',
name='downlink',
field=models.FloatField(blank=True, null=True, verbose_name='Downlink'),
),
migrations.AddField(
model_name='transponders',
name='uplink',
field=models.FloatField(blank=True, null=True, verbose_name='Uplink'),
),
migrations.AddField(
model_name='transponders',
name='transfer',
field=models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.functions.math.Abs(django.db.models.expressions.CombinedExpression(models.F('downlink'), '-', models.F('uplink'))), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и гео, км'),
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-27 13:10
import django.db.models.expressions
import django.db.models.functions.math
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mapsapp', '0002_remove_transponders_frequency_transponders_downlink_and_more'),
]
operations = [
migrations.AlterField(
model_name='transponders',
name='transfer',
field=models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.functions.math.Abs(django.db.models.expressions.CombinedExpression(models.F('downlink'), '-', models.F('uplink'))), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Перенос'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-28 05:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mapsapp', '0003_alter_transponders_transfer'),
]
operations = [
migrations.AlterField(
model_name='transponders',
name='zone_name',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Название зоны'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-29 14:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mapsapp', '0004_alter_transponders_zone_name'),
]
operations = [
migrations.AlterField(
model_name='transponders',
name='frequency_range',
field=models.FloatField(blank=True, null=True, verbose_name='Полоса'),
),
]