diff --git a/stratoflights/settings.py b/stratoflights/settings.py index bb5b63e..224cb3a 100644 --- a/stratoflights/settings.py +++ b/stratoflights/settings.py @@ -45,6 +45,7 @@ MEDIA_ROOT = os.getenv('MEDIA_ROOT', os.path.join(BASE_DIR, 'media')) # Куд # Application definition INSTALLED_APPS = [ + 'daphne', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -103,7 +104,7 @@ ASGI_APPLICATION = 'stratoflights.asgi.application' # Database # https://docs.djangoproject.com/en/4.2/ref/settings/#databases -if PRODUCTION: +if not PRODUCTION: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -167,7 +168,7 @@ AUTH_USER_MODEL = 'stratoflights_api.User' REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', - 'PAGE_SIZE': 100, + 'PAGE_SIZE': 1000, 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', @@ -196,9 +197,9 @@ CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:5173, CHANNEL_LAYERS = { "default": { - "BACKEND": "channels_redis.core.RedisChannelLayer", - "CONFIG": { - "hosts": [("redis", 6379)], - }, + "BACKEND": "channels.layers.InMemoryChannelLayer", + # "CONFIG": { + # "hosts": [("redis", 6379)], + # }, }, } \ No newline at end of file diff --git a/stratoflights_api/custom_pagination.py b/stratoflights_api/custom_pagination.py index 6d37de8..2e5070b 100644 --- a/stratoflights_api/custom_pagination.py +++ b/stratoflights_api/custom_pagination.py @@ -4,7 +4,7 @@ from rest_framework.response import Response class CustomLimitOffsetPagination(LimitOffsetPagination): limit_query_param = 'limit' offset_query_param = 'skip' - max_limit = 100 + max_limit = 1000 default_limit = 10 diff --git a/stratoflights_api/migrations/0002_telemetrypacket_user_nullable.py b/stratoflights_api/migrations/0002_telemetrypacket_user_nullable.py new file mode 100644 index 0000000..b3d2e4a --- /dev/null +++ b/stratoflights_api/migrations/0002_telemetrypacket_user_nullable.py @@ -0,0 +1,24 @@ +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stratoflights_api', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='telemetrypacket', + name='user', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/stratoflights_api/models.py b/stratoflights_api/models.py index 9a331c7..cdeeb12 100644 --- a/stratoflights_api/models.py +++ b/stratoflights_api/models.py @@ -39,7 +39,7 @@ class Satellite(models.Model): class TelemetryPacket(models.Model): user = models.ForeignKey( - get_user_model(), on_delete=models.CASCADE, default=0) + get_user_model(), on_delete=models.SET_NULL, null=True, blank=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) satellite = models.ForeignKey( Satellite, on_delete=models.CASCADE, related_name="telemetry") @@ -50,6 +50,10 @@ class TelemetryPacket(models.Model): payload = models.JSONField(blank=True, default=dict) raw_data = models.JSONField(blank=True, default=dict) created_at = models.DateTimeField(auto_now_add=True) + def __str__(self): + return f"packet {self.satellite} {self.lat} {self.lon} {self.alt} {self.timestamp}" + class Meta: + ordering = ["-timestamp"] class PreditctionTemplate(models.Model): diff --git a/stratoflights_api/serializers.py b/stratoflights_api/serializers.py index dacdf57..4c77cf0 100644 --- a/stratoflights_api/serializers.py +++ b/stratoflights_api/serializers.py @@ -124,6 +124,7 @@ class TelemetryPacketSerializer(serializers.ModelSerializer): model = TelemetryPacket fields = ['id', 'timestamp', 'lat', 'lon', 'alt', 'payload'] read_only_fields = ['id'] + extra_kwargs = {'timestamp': {'required': False}} class SavedPointSerializer(serializers.ModelSerializer): diff --git a/stratoflights_api/services/tawhiri.py b/stratoflights_api/services/tawhiri.py index 44d1fb0..5a6bf04 100644 --- a/stratoflights_api/services/tawhiri.py +++ b/stratoflights_api/services/tawhiri.py @@ -6,7 +6,7 @@ from zoneinfo import ZoneInfo from collections import OrderedDict class TawhiriClient: - BASE_URL = "https://fly.stratonautica.ru/api/v2/" + BASE_URL = "http://127.0.0.1:8080/api/v1/prediction" TIMEOUT = 15 @staticmethod diff --git a/stratoflights_api/views.py b/stratoflights_api/views.py index 319f2a1..b358bd2 100644 --- a/stratoflights_api/views.py +++ b/stratoflights_api/views.py @@ -156,16 +156,47 @@ class TelemetryListCreateView(generics.ListCreateAPIView): if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + try: + satellite = Satellite.objects.get(id=pk) + except Satellite.DoesNotExist: + return Response({"detail": "Satellite not found"}, status=status.HTTP_404_NOT_FOUND) + validated_data = serializer.validated_data - TelemetryPacket.objects.create(timestamp=time.time(), - satellite=Satellite.objects.get(id=pk), - lat=validated_data["lat"], - lon=validated_data["lon"], - alt=validated_data["alt"], - payload=validated_data['payload'], - ) - return Response(serializer.errors, status=status.HTTP_201_CREATED) + packet = TelemetryPacket.objects.create( + satellite=satellite, + user=request.user if request.user.is_authenticated else None, + timestamp=validated_data.get('timestamp', int(time.time())), + lat=validated_data['lat'], + lon=validated_data['lon'], + alt=validated_data['alt'], + payload=validated_data.get('payload', {}), + ) + + # Broadcast to WebSocket subscribers so the tracking page updates live + try: + from asgiref.sync import async_to_sync + from channels.layers import get_channel_layer + from .consumers import SatelliteTelemetryConsumer + channel_layer = get_channel_layer() + if channel_layer is not None: + async_to_sync(SatelliteTelemetryConsumer.broadcast_to_satellite_group)( + str(pk), + { + 'id': str(packet.id), + 'timestamp': packet.timestamp, + 'lat': packet.lat, + 'lon': packet.lon, + 'alt': packet.alt, + 'payload': packet.payload, + 'raw_data': {}, + }, + channel_layer, + ) + except Exception: + pass # WS broadcast is best-effort; don't fail the REST response + + return Response({'id': str(packet.id)}, status=status.HTTP_201_CREATED) class SessionView(APIView):