added api/pk/satellite, authorisation endpointsm, pagination
This commit is contained in:
parent
cc5187c3a1
commit
8225b18a2a
16 changed files with 284 additions and 26 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,7 +1,9 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import User, Prediction, UserPrediction
|
||||
from .models import User, Prediction, UserPrediction, Satellite, TelemetryPacket
|
||||
|
||||
admin.site.register(User)
|
||||
admin.site.register(Prediction)
|
||||
admin.site.register(UserPrediction)
|
||||
admin.site.register(UserPrediction)
|
||||
admin.site.register(Satellite)
|
||||
admin.site.register(TelemetryPacket)
|
||||
35
api/migrations/0004_satellite_telemetrypacket.py
Normal file
35
api/migrations/0004_satellite_telemetrypacket.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 5.1.7 on 2025-04-05 13:30
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0003_alter_userprediction_unique_together_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Satellite',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TelemetryPacket',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('timestamp', models.BigIntegerField()),
|
||||
('lat', models.FloatField()),
|
||||
('lon', models.FloatField()),
|
||||
('alt', models.FloatField()),
|
||||
('payload', models.JSONField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('satellite', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='telemetry', to='api.satellite')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -18,3 +18,16 @@ class UserPrediction(models.Model):
|
|||
prediction = models.ForeignKey("Prediction", on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField()
|
||||
|
||||
class Satellite(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
class TelemetryPacket(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
satellite = models.ForeignKey(Satellite, on_delete=models.CASCADE, related_name="telemetry")
|
||||
timestamp = models.BigIntegerField() # unix time
|
||||
lat = models.FloatField()
|
||||
lon = models.FloatField()
|
||||
alt = models.FloatField()
|
||||
payload = models.JSONField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
8
api/permissions.py
Normal file
8
api/permissions.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from rest_framework.permissions import BasePermission, SAFE_METHODS
|
||||
|
||||
class ReadOnlyOrAuthenticated(BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
return (
|
||||
request.method in SAFE_METHODS or
|
||||
request.user and request.user.is_authenticated
|
||||
)
|
||||
|
|
@ -84,3 +84,13 @@ class PredictionDetailSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Prediction
|
||||
fields = ["id", "created_at", "updated_at", "result"]
|
||||
|
||||
|
||||
from rest_framework import serializers
|
||||
from .models import TelemetryPacket
|
||||
|
||||
class TelemetryPacketSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = TelemetryPacket
|
||||
fields = ['id', 'timestamp', 'lat', 'lon', 'alt', 'payload']
|
||||
|
||||
|
|
|
|||
26
api/urls.py
26
api/urls.py
|
|
@ -1,16 +1,26 @@
|
|||
from django.urls import path
|
||||
from .views import (PredictionCreateView, PredictionListView, PredictionDeleteView,
|
||||
from .views import (PredictionCreateView, PredictionListView,
|
||||
PredictionHistoryListView,
|
||||
PredictionHistoryDetailView,
|
||||
PredictionHistoryDeleteView,)
|
||||
PredictionHistoryDeleteView,
|
||||
SessionView,
|
||||
WhoAmIView,
|
||||
get_csrf,
|
||||
login_view,
|
||||
logout_view)
|
||||
from rest_framework.authtoken.views import obtain_auth_token
|
||||
|
||||
from .views import TelemetryListCreateView
|
||||
urlpatterns = [
|
||||
path('predictions', PredictionCreateView.as_view(), name='create_prediction'),
|
||||
path('predictions', PredictionListView.as_view(), name='get_predictions'),
|
||||
path('predictions/<uuid:pk>', PredictionDeleteView.as_view(), name='delete_prediction'),
|
||||
path('token/', obtain_auth_token),
|
||||
path("history/", PredictionHistoryListView.as_view()),
|
||||
path("history/<uuid:pk>/", PredictionHistoryDetailView.as_view()),
|
||||
path("history/<uuid:pk>/delete/", PredictionHistoryDeleteView.as_view()),
|
||||
path('token', obtain_auth_token, name = 'get_token'),
|
||||
path("history", PredictionHistoryListView.as_view(), name='view_history_list'),
|
||||
path("history/<uuid:pk>/", PredictionHistoryDetailView.as_view(), name='view_history_detail'),
|
||||
path("history/<uuid:pk>/delete/", PredictionHistoryDeleteView.as_view(), name='delete_history'),
|
||||
path("<uuid:pk>/telemetry/", TelemetryListCreateView.as_view(), name="create_telemetry"),
|
||||
path('csrf/', get_csrf, name='api-csrf'),
|
||||
path('login/', login_view, name='api-login'),
|
||||
path('logout/', logout_view, name='api-logout'),
|
||||
path('session/', SessionView.as_view(), name='api-session'), # new
|
||||
path('whoami/', WhoAmIView.as_view(), name='api-whoami'), # new
|
||||
]
|
||||
|
|
|
|||
129
api/views.py
129
api/views.py
|
|
@ -11,6 +11,15 @@ from django.utils.decorators import method_decorator
|
|||
from rest_framework.permissions import AllowAny
|
||||
from .services.tawhiri import TawhiriClient
|
||||
from django.contrib.auth import get_user_model
|
||||
from .models import Satellite, TelemetryPacket
|
||||
from .serializers import TelemetryPacketSerializer
|
||||
from .permissions import ReadOnlyOrAuthenticated
|
||||
import time
|
||||
from django.http import JsonResponse
|
||||
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
import json
|
||||
from django.middleware.csrf import get_token
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
|
@ -24,6 +33,8 @@ def get_prediction_from_tawhiri(params):
|
|||
else:
|
||||
raise Exception(f"Tawhiri error: {response.status_code} {response.text}")
|
||||
|
||||
|
||||
|
||||
class PredictionCreateView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
|
|
@ -39,19 +50,20 @@ class PredictionCreateView(APIView):
|
|||
|
||||
try:
|
||||
prediction_result = TawhiriClient.get_prediction(validated_data)
|
||||
print(prediction_result)
|
||||
except requests.RequestException as e:
|
||||
print("❌ Tawhiri error:", str(e), e.response.text if e.response else "no response")
|
||||
print("Tawhiri error:", str(e), e.response.text if e.response else "no response")
|
||||
return Response({"error": f"Tawhiri error: {str(e)}"}, status=status.HTTP_502_BAD_GATEWAY)
|
||||
|
||||
prediction = Prediction.objects.create(result=prediction_result)
|
||||
UserPrediction.objects.create(user=request.user, prediction=prediction, created_at=timezone.now())
|
||||
|
||||
return Response({
|
||||
"id": prediction.id,
|
||||
"created_at": prediction.created_at,
|
||||
"result": prediction_result
|
||||
}, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class PredictionListView(APIView):
|
||||
def get(self, request):
|
||||
user_id = request.query_params.get('user_id')
|
||||
|
|
@ -66,17 +78,6 @@ class PredictionListView(APIView):
|
|||
)
|
||||
return Response(PredictionSerializer(predictions, many=True).data)
|
||||
|
||||
class PredictionDeleteView(APIView):
|
||||
def delete(self, request, pk):
|
||||
try:
|
||||
prediction = Prediction.objects.get(pk=pk)
|
||||
prediction.deleted_at = timezone.now()
|
||||
prediction.save()
|
||||
return Response({"deleted": True})
|
||||
except Prediction.DoesNotExist:
|
||||
return Response({"error": "Not found"}, status=404)
|
||||
|
||||
|
||||
|
||||
class PredictionHistoryListView(generics.ListAPIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
|
@ -89,6 +90,7 @@ class PredictionHistoryListView(generics.ListAPIView):
|
|||
)
|
||||
|
||||
|
||||
|
||||
class PredictionHistoryDetailView(generics.RetrieveAPIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
serializer_class = PredictionDetailSerializer
|
||||
|
|
@ -100,6 +102,7 @@ class PredictionHistoryDetailView(generics.RetrieveAPIView):
|
|||
)
|
||||
|
||||
|
||||
|
||||
class PredictionHistoryDeleteView(generics.DestroyAPIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
|
|
@ -114,5 +117,103 @@ class PredictionHistoryDeleteView(generics.DestroyAPIView):
|
|||
instance.save()
|
||||
|
||||
|
||||
|
||||
class TelemetryListCreateView(generics.ListCreateAPIView):
|
||||
serializer_class = TelemetryPacketSerializer
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get_queryset(self):
|
||||
qs = TelemetryPacket.objects.filter(satellite_id=self.kwargs["pk"])
|
||||
|
||||
from_ts = self.request.query_params.get("from")
|
||||
till_ts = self.request.query_params.get("till")
|
||||
|
||||
if from_ts:
|
||||
qs = qs.filter(timestamp__gte=int(from_ts))
|
||||
if till_ts:
|
||||
qs = qs.filter(timestamp__lte=int(till_ts))
|
||||
|
||||
return qs.order_by("-timestamp")
|
||||
|
||||
|
||||
|
||||
def post(self, request, pk):
|
||||
serializer = TelemetryPacketSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class SessionView(APIView):
|
||||
authentication_classes = [SessionAuthentication, BasicAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@staticmethod
|
||||
def get(request, format=None):
|
||||
return JsonResponse({'isAuthenticated': True})
|
||||
|
||||
|
||||
class WhoAmIView(APIView):
|
||||
authentication_classes = [SessionAuthentication, BasicAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@staticmethod
|
||||
def get(request, format=None):
|
||||
return JsonResponse({'username': request.user.username})
|
||||
|
||||
|
||||
def get_csrf(request):
|
||||
response = JsonResponse({'detail': 'CSRF cookie set'})
|
||||
response['X-CSRFToken'] = get_token(request)
|
||||
return response
|
||||
|
||||
|
||||
def login_view(request):
|
||||
data = json.loads(request.body)
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
|
||||
if username is None or password is None:
|
||||
return JsonResponse({'detail': 'Please provide username and password.'}, status=400)
|
||||
|
||||
user = authenticate(username=username, password=password)
|
||||
|
||||
if user is None:
|
||||
return JsonResponse({'detail': 'Invalid credentials.'}, status=400)
|
||||
|
||||
login(request, user)
|
||||
return JsonResponse({'detail': 'Successfully logged in.'})
|
||||
|
||||
|
||||
def logout_view(request):
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'detail': 'You\'re not logged in.'}, status=400)
|
||||
|
||||
logout(request)
|
||||
return JsonResponse({'detail': 'Successfully logged out.'})
|
||||
|
||||
|
||||
|
||||
#class PredictionCreateView(APIView):
|
||||
#permission_classes = [IsAuthenticated]
|
||||
#permission_classes = [IsAuthenticated]
|
||||
|
||||
# class TelemetryPacket(models.Model):
|
||||
# id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
# satellite = models.ForeignKey(Satellite, on_delete=models.CASCADE, related_name="telemetry")
|
||||
# timestamp = models.BigIntegerField() # unix time
|
||||
# lat = models.FloatField()
|
||||
# lon = models.FloatField()
|
||||
# alt = models.FloatField()
|
||||
# payload = models.JSONField(null=True, blank=True)
|
||||
# created_at = models.DateTimeField(auto_now_add=True)
|
||||
# fields = ['id', 'timestamp', 'lat', 'lon', 'alt', 'payload']
|
||||
Loading…
Add table
Add a link
Reference in a new issue