"""SatNOGS DB API django rest framework Views""" from django.conf import settings from django.core.cache import cache from django.core.files.base import ContentFile from django.db.models import F from django.shortcuts import get_object_or_404 from django.utils.timezone import now from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiExample, OpenApiParameter, extend_schema, \ extend_schema_view from rest_framework import mixins, status, viewsets from rest_framework.parsers import FileUploadParser, FormParser, MultiPartParser from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer from rest_framework.response import Response from rest_framework.serializers import ValidationError from db.api import filters, pagination, serializers from db.api.parsers import JSONLDParser from db.api.perms import IsAuthenticatedOrOptions, SafeMethodsWithPermission from db.api.renderers import BrowserableJSONLDRenderer, JSONLDRenderer from db.api.throttling import GetTelemetryAnononymousRateThrottle, GetTelemetryUserRateThrottle, \ GetTelemetryViolatorThrottle from db.base.helpers import gridsquare from db.base.models import SATELLITE_STATUS, SERVICE_TYPE, TRANSMITTER_STATUS, TRANSMITTER_TYPE, \ Artifact, DemodData, LatestTleSet, Mode, Satellite, SatelliteEntry, SatelliteIdentifier, \ Transmitter from db.base.tasks import decode_current_frame, publish_current_frame, update_satellite_name ISS_EXAMPLE = OpenApiExample('25544 (ISS)', value=25544) @extend_schema_view( retrieve=extend_schema( description='Retrieve a single RF Mode from SatNOGS DB based on its ID', ), list=extend_schema(description='Retrieve a complete list of RF Modes from SatNOGS DB', ) ) class ModeViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901 """ Read-only view into the transmitter modulation modes (RF Modes) currently tracked in the SatNOGS DB database For more details on individual RF mode types please [see our wiki][moderef]. [moderef]: https://wiki.satnogs.org/Category:RF_Modes """ renderer_classes = [ JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer ] queryset = Mode.objects.all() serializer_class = serializers.ModeSerializer @extend_schema_view( list=extend_schema( description='Retrieve a full or filtered list of satellites in SatNOGS DB', parameters=[ # drf-spectacular does not currently recognize the in_orbit filter as a # bool, forcing it here. See drf-spectacular#234 OpenApiParameter( name='in_orbit', description='Filter by satellites currently in orbit (True) or those that have \ decayed (False)', required=False, type=bool ), OpenApiParameter( name='status', description='Filter by satellite status: ' + ' '.join(SATELLITE_STATUS), required=False, type=OpenApiTypes.STR ), OpenApiParameter( name='norad_cat_id', description='Select a satellite by its NORAD-assigned identifier', examples=[ISS_EXAMPLE], ), ], ), retrieve=extend_schema( description='Retrieve details on a single satellite in SatNOGS DB', parameters=[ OpenApiParameter( 'satellite_identifier__sat_id', OpenApiTypes.STR, OpenApiParameter.PATH, description='Select a satellite by its Satellite Identifier', ), ], ), ) class SatelliteViewSet( # pylint: disable=R0901 mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): """ View into the Satellite entities in the SatNOGS DB database """ renderer_classes = [ JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer ] parser_classes = [JSONLDParser] queryset = Satellite.objects.filter( associated_satellite__isnull=True, satellite_entry__approved=True ).prefetch_related('associated_with', 'telemetries') serializer_class = serializers.SatelliteSerializer filterset_class = filters.SatelliteViewFilter lookup_field = 'satellite_identifier__sat_id' def get_object(self): queryset = self.get_queryset() # Apply any filter backends queryset = self.filter_queryset(queryset) # In case user uses NORAD ID for getting satellite if 'satellite_entry__norad_cat_id' in self.kwargs: norad_cat_id = self.kwargs['satellite_entry__norad_cat_id'] return get_object_or_404(queryset, satellite_entry__norad_cat_id=norad_cat_id) # Getting satellite by using Satellite Identifier sat_id = self.kwargs['satellite_identifier__sat_id'] try: return queryset.get(satellite_identifier__sat_id=sat_id) except Satellite.DoesNotExist: return get_object_or_404( queryset, associated_with__satellite_identifier__sat_id=sat_id ) def create(self, request, *args, **kwargs): # noqa: C901; pylint: disable=R0911,R0912,R0915 """ Creates a satellite suggestion. """ satellites_data = [] for satellite_entry in request.data['@graph']: if 'satellite' not in satellite_entry: data = 'Satellite Entry without "satellite" key' return Response(data, status=status.HTTP_400_BAD_REQUEST) if not satellite_entry['satellite']: data = 'One or more of the required fields are missing.\n Required fields: \ name, status, citation' return Response(data, status=status.HTTP_400_BAD_REQUEST) satellite = satellite_entry['satellite'] create_satellite_identifier = False satellite_data = {} if "@id" not in satellite: data = 'Missing "@id" for one or more entries' return Response(data, status=status.HTTP_400_BAD_REQUEST) if 'sat_id' in satellite: if isinstance(satellite['sat_id'], list): data = 'Multiple values for "http://schema.org/identifier" or multiple \ entries with the same "@id" and different \ "http://schema.org/identifier" values' return Response(data, status=status.HTTP_400_BAD_REQUEST) sat_id = satellite['sat_id'] try: satellite_object = Satellite.objects.get(satellite_identifier__sat_id=sat_id) satellite_data['satellite_identifier' ] = satellite_object.satellite_entry.satellite_identifier.pk satellite_data['norad_follow_id' ] = satellite_object.satellite_entry.norad_follow_id satellite_data['description'] = satellite_object.satellite_entry.description satellite_data['dashboard_url' ] = satellite_object.satellite_entry.dashboard_url satellite_data['image'] = satellite_object.satellite_entry.image satellite_data['decayed'] = satellite_object.satellite_entry.decayed satellite_data['countries'] = satellite_object.satellite_entry.countries satellite_data['website'] = satellite_object.satellite_entry.website satellite_data['launched'] = satellite_object.satellite_entry.launched satellite_data['deployed'] = satellite_object.satellite_entry.deployed satellite_data['operator'] = satellite_object.satellite_entry.operator except Satellite.DoesNotExist: try: satellite_identifier = SatelliteIdentifier.objects.get(sat_id=sat_id) satellite_data['satellite_identifier'] = satellite_identifier.pk except SatelliteIdentifier.DoesNotExist: satellite_identifier = SatelliteIdentifier.objects.create(sat_id=sat_id) satellite_data['satellite_identifier'] = satellite_identifier.pk else: create_satellite_identifier = True if isinstance(satellite['status'], list): data = 'Multiple values for "https://schema.space/metasat/status" or multiple \ entries with the same "@id" and different \ "https://schema.space/metasat/status" values' return Response(data, status=status.HTTP_400_BAD_REQUEST) if isinstance(satellite['name'], list): data = 'Multiple values for "https://schema.space/metasat/name" or multiple \ entries with the same "@id" and different \ "https://schema.space/metasat/name" values' return Response(data, status=status.HTTP_400_BAD_REQUEST) if isinstance(satellite['citation'], list): data = 'Multiple values for "https://schema.org/citation" or multiple \ entries with the same "@id" and different \ "https://schema.org/citation" values' return Response(data, status=status.HTTP_400_BAD_REQUEST) satellite_data['name'] = satellite['name'] satellite_data['status'] = satellite['status'] satellite_data['citation'] = satellite['citation'] satellite_data['created_by'] = request.user.pk if 'norad_cat_id' in satellite: if isinstance(satellite['norad_cat_id'], list): data = 'Multiple values for "https://schema.space/metasat/noradID" or \ multiple entries with the same "@id" and different \ "https://schema.space/metasat/noradId" values' return Response(data, status=status.HTTP_400_BAD_REQUEST) satellite_data['norad_cat_id'] = satellite['norad_cat_id'] if 'names' in satellite: if isinstance(satellite['norad_cat_id'], list): satellite_data['names'] = '\r\n'.join(satellite['names']) else: satellite_data['names'] = satellite['names'] if create_satellite_identifier: satellite_identifier = SatelliteIdentifier.objects.create() satellite_data['satellite_identifier'] = satellite_identifier.pk satellites_data.append(satellite_data) serializer = serializers.SatelliteEntrySerializer( data=satellites_data, many=True, allow_empty=True ) if serializer.is_valid(): serializer.save() else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_201_CREATED) @extend_schema_view( list=extend_schema( parameters=[ OpenApiParameter( name='satellite__norad_cat_id', description='NORAD ID of a satellite to filter telemetry data for', examples=[ISS_EXAMPLE], ), OpenApiParameter( name='status', description='Filter by transmitter status: ' + ' '.join(TRANSMITTER_STATUS), required=False, type=OpenApiTypes.STR, examples=[OpenApiExample('active', value='\'active\'')] ), OpenApiParameter( name='service', description='Filter by transmitter service: ' + ' '.join(SERVICE_TYPE), required=False, type=OpenApiTypes.STR, examples=[OpenApiExample('Amateur', value='\'Amateur\'')] ), OpenApiParameter( name='type', description='Filter by transmitter type: ' + ' '.join(TRANSMITTER_TYPE), required=False, type=OpenApiTypes.STR, examples=[OpenApiExample('Transmitter', value='\'Transmitter\'')] ), ], ), ) class TransmitterViewSet( # pylint: disable=R0901 mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): """ View into the Transmitter entities in the SatNOGS DB database. Transmitters are inclusive of Transceivers and Transponders """ renderer_classes = [ JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer ] parser_classes = [JSONLDParser] queryset = Transmitter.objects.filter( satellite__satellite_entry__approved=True, satellite__associated_satellite__isnull=True ).exclude(status='invalid') serializer_class = serializers.TransmitterSerializer filterset_class = filters.TransmitterViewFilter lookup_field = 'uuid' def create(self, request, *args, **kwargs): # noqa: C901; pylint: disable=R0911,R0912,R0915 """ Creates a transmitter suggestion. """ transmitters_data = [] for transmitter_entry in request.data['@graph']: if 'transmitter' not in transmitter_entry: data = 'Transmitter Entry without "transmitter" key' return Response(data, status=status.HTTP_400_BAD_REQUEST) if not transmitter_entry['transmitter']: data = 'One or more of the required fields are missing.\n Required fields: \ description, status, citation, service, satellite' return Response(data, status=status.HTTP_400_BAD_REQUEST) transmitter = transmitter_entry['transmitter'] transmitter_data = {} if "@id" not in transmitter: data = 'Missing "@id" for one or more entries' return Response(data, status=status.HTTP_400_BAD_REQUEST) if 'uuid' in transmitter: if isinstance(transmitter['uuid'], list): data = 'Multiple values for "http://schema.org/identifier" or multiple \ entries with the same "@id" and different \ "http://schema.org/identifier" values' return Response(data, status=status.HTTP_400_BAD_REQUEST) transmitter_uuid = transmitter['uuid'] if Transmitter.objects.filter(uuid=transmitter_uuid).exists(): transmitter_data['uuid'] = transmitter_uuid transmitter_data['description'] = transmitter['description'] transmitter_data['status'] = transmitter['status'] transmitter_data['citation'] = transmitter['citation'] transmitter_data['service'] = transmitter['service'] transmitter_data['created_by'] = request.user.pk try: if transmitter['satellite']: if isinstance(transmitter['satellite'], list): data = 'Multiple values for "https://schema.space/metasat/satellite" \ or multiple entries with the same "@id" and different \ "https://schema.space/metasat/satellite" values' return Response(data, status=status.HTTP_400_BAD_REQUEST) transmitter_data['satellite'] = Satellite.objects.get( satellite_entry__norad_cat_id=transmitter['satellite']['norad_cat_id'] ).pk else: data = 'Missing NORAD ID value for Satellite' return Response(data, status=status.HTTP_400_BAD_REQUEST) except Satellite.DoesNotExist: data = 'Unknown NORAD ID: {}'.format(transmitter['satellite']['norad_cat_id']) return Response(data, status=status.HTTP_400_BAD_REQUEST) if 'baud' in transmitter: transmitter_data['baud'] = transmitter['baud'] if 'invert' in transmitter: transmitter_data['invert'] = transmitter['invert'] if 'uplink' not in transmitter and 'downlink' not in transmitter: data = 'Missing "https://schema.space/metasat/uplink" or \ "https://schema.space/metasat/downlink"' return Response(data, status=status.HTTP_400_BAD_REQUEST) if 'uplink' in transmitter: if isinstance(transmitter['uplink'], list): data = 'Multiple values for "https://schema.space/metasat/uplink" or multiple \ entries with the same "@id" and different \ "https://schema.space/metasat/uplink" values' return Response(data, status=status.HTTP_400_BAD_REQUEST) if 'frequency' not in transmitter['uplink']: data = 'Missing "https://schema.space/metasat/frequency" from \ "https://schema.space/metasat/uplink" value' return Response(data, status=status.HTTP_400_BAD_REQUEST) if isinstance(transmitter['uplink']['frequency'], int): transmitter_data['type'] = 'Transceiver' transmitter_data['uplink_low'] = transmitter['uplink']['frequency'] else: transmitter_data['type'] = 'Transponder' if 'minimum' not in transmitter['uplink'][ 'frequency'] or 'maximum' not in transmitter['uplink']['frequency']: data = 'Missing "https://schema.org/minimum" or \ "https://schema.org/maximum" from \ "https://schema.space/metasat/frequency" value of \ "https://schema.space/metasat/uplink"' return Response(data, status=status.HTTP_400_BAD_REQUEST) transmitter_data['uplink_low'] = transmitter['uplink']['frequency']['minimum'] transmitter_data['uplink_high'] = transmitter['uplink']['frequency']['maximum'] if 'mode' in transmitter['uplink']: try: transmitter_data['uplink_mode'] = Mode.objects.get( name=transmitter['uplink']['mode'] ).pk except Mode.DoesNotExist: data = 'Unknown Mode: {}'.format(transmitter['uplink']['mode']) return Response(data, status=status.HTTP_400_BAD_REQUEST) if 'drift' in transmitter['uplink']: transmitter_data['uplink_drift'] = transmitter['uplink']['drift'] else: transmitter_data['type'] = 'Transmitter' if 'downlink' in transmitter: if isinstance(transmitter['downlink'], list): data = 'Multiple values for "https://schema.space/metasat/downlink" or \ multiple entries with the same "@id" and different \ "https://schema.space/metasat/downlink" values' return Response(data, status=status.HTTP_400_BAD_REQUEST) if 'frequency' not in transmitter['downlink']: data = 'Missing "https://schema.space/metasat/frequency" from \ "https://schema.space/metasat/downlink" value' return Response(data, status=status.HTTP_400_BAD_REQUEST) if isinstance(transmitter['downlink']['frequency'], int) and not transmitter_data['type'] == 'Transponder': transmitter_data['downlink_low'] = transmitter['downlink']['frequency'] elif transmitter_data['type'] == 'Transponder': if 'minimum' not in transmitter['downlink'][ 'frequency'] or 'maximum' not in transmitter['downlink']['frequency']: data = 'Missing "https://schema.org/minimum" or \ "https://schema.org/maximum" from \ "https://schema.space/metasat/frequency" value of \ "https://schema.space/metasat/downlink"' return Response(data, status=status.HTTP_400_BAD_REQUEST) transmitter_data['downlink_low'] = transmitter['downlink']['frequency'][ 'minimum'] transmitter_data['downlink_high'] = transmitter['downlink']['frequency'][ 'maximum'] else: data = 'Expected integer for "https://schema.space/metasat/frequency" value \ of "https://schema.space/metasat/downlink"' return Response(data, status=status.HTTP_400_BAD_REQUEST) if 'mode' in transmitter['downlink']: try: transmitter_data['downlink_mode'] = Mode.objects.get( name=transmitter['downlink']['mode'] ).pk except Mode.DoesNotExist: data = 'Unknown Mode: {}'.format(transmitter['downlink']['mode']) return Response(data, status=status.HTTP_400_BAD_REQUEST) if 'drift' in transmitter['downlink']: transmitter_data['downlink_drift'] = transmitter['downlink']['drift'] transmitters_data.append(transmitter_data) serializer = serializers.TransmitterEntrySerializer( data=transmitters_data, many=True, allow_empty=True ) if serializer.is_valid(): serializer.save() else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_201_CREATED) class LatestTleSetViewSet(viewsets.ReadOnlyModelViewSet): # pylint: disable=R0901 """ Read-only view into the most recent two-line elements (TLE) in the SatNOGS DB database """ renderer_classes = [JSONRenderer, BrowsableAPIRenderer] queryset = LatestTleSet.objects.all().select_related('satellite').exclude( latest_distributable__isnull=True ).annotate( tle0=F('latest_distributable__tle0'), tle1=F('latest_distributable__tle1'), tle2=F('latest_distributable__tle2'), tle_source=F('latest_distributable__tle_source'), updated=F('latest_distributable__updated') ) serializer_class = serializers.LatestTleSetSerializer filterset_class = filters.LatestTleSetViewFilter def get_queryset(self): """ Returns latest TLE queryset depending on user permissions """ if self.request.user.has_perm('base.access_all_tles'): return LatestTleSet.objects.all().select_related('satellite').exclude( latest__isnull=True ).annotate( tle0=F('latest__tle0'), tle1=F('latest__tle1'), tle2=F('latest__tle2'), tle_source=F('latest__tle_source'), updated=F('latest__updated') ) return self.queryset @extend_schema_view( list=extend_schema( parameters=[ OpenApiParameter( name='app_source', description='The submission source for the telemetry frames: manual (a manual \ upload/entry), network (SatNOGS Network observations), or sids \ (legacy API submission)', ), OpenApiParameter( name='observer', description='(string) name of the observer (submitter) to retrieve telemetry data \ from' ), OpenApiParameter( name='satellite', description='NORAD ID of a satellite to filter telemetry data for', examples=[ISS_EXAMPLE], ), OpenApiParameter(name='transmitter', description='Not currently in use'), ], ), ) class TelemetryViewSet( # pylint: disable=R0901,R0912,R0915 mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): """ View into the Telemetry objects in the SatNOGS DB database. Currently, this table is inclusive of all data collected from satellite downlink observations """ renderer_classes = [ JSONRenderer, BrowsableAPIRenderer, JSONLDRenderer, BrowserableJSONLDRenderer ] queryset = DemodData.objects.all().select_related('satellite', 'transmitter') serializer_class = serializers.TelemetrySerializer filterset_class = filters.TelemetryViewFilter permission_classes = [SafeMethodsWithPermission] throttle_classes = [ GetTelemetryAnononymousRateThrottle, GetTelemetryUserRateThrottle, GetTelemetryViolatorThrottle ] parser_classes = (FormParser, MultiPartParser, FileUploadParser) pagination_class = pagination.LinkedHeaderPageNumberPagination def list(self, request, *args, **kwargs): """ Lists data from satellite if they are filtered by NORAD ID or Satellite ID. Also logs the requests if it is set to do so. """ satellite = request.query_params.get('satellite', None) sat_id = request.query_params.get('sat_id', None) if not (satellite or sat_id): data = { 'detail': ( 'For getting data please use either satellite(NORAD ID) filter or' 'sat_id(Satellite ID) filter' ), 'results': None } response = Response(data, status=status.HTTP_400_BAD_REQUEST) response.exception = True return response if settings.LOG_TELEMETRY_REQUESTS: user_id = str(request.user.id) remote_address = str(request.META.get("REMOTE_ADDR")) x_forwarded_for = str(request.META.get("HTTP_X_FORWARDED_FOR")) timestamp = now().isoformat() request_data = user_id + ';' + remote_address + ';' + x_forwarded_for + ';' + timestamp cache.set( 'telemetry_log_' + user_id + '_' + timestamp, request_data, settings.TELEMETRY_LOGS_TIME_TO_LIVE ) return super().list(request, *args, **kwargs) @extend_schema( responses={'201': None}, # None ) def create(self, request, *args, **kwargs): """ Creates a frame of telemetry data from a satellite observation. """ # pylint: disable=R0914 data = {} norad_id = request.data.get('noradID') try: if norad_id: satellite = Satellite.objects.get(satellite_entry__norad_cat_id=norad_id) else: raise ValueError except Satellite.DoesNotExist: satellite_identifier = SatelliteIdentifier.objects.create() satellite_entry = SatelliteEntry.objects.create( norad_cat_id=norad_id, name='New Satellite', satellite_identifier=satellite_identifier, created=now() ) satellite = Satellite.objects.create( satellite_identifier=satellite_identifier, satellite_entry=satellite_entry ) update_satellite_name.delay(int(norad_id)) except ValueError: return Response(status=status.HTTP_400_BAD_REQUEST) data['satellite'] = satellite.id data['station'] = request.data.get('source') data['timestamp'] = request.data.get('timestamp') if request.data.get('version'): data['version'] = request.data.get('version') observation_id = '' if request.data.get('observation_id'): observation_id = request.data.get('observation_id') data['observation_id'] = observation_id station_id = '' if request.data.get('station_id'): station_id = request.data.get('station_id') data['station_id'] = station_id # Convert coordinates to omit N-S and W-E designators lat = request.data.get('latitude') lng = request.data.get('longitude') try: if any(x.isalpha() for x in lat): data['lat'] = (-float(lat[:-1]) if ('S' in lat) else float(lat[:-1])) else: data['lat'] = float(lat) if any(x.isalpha() for x in lng): data['lng'] = (-float(lng[:-1]) if ('W' in lng) else float(lng[:-1])) else: data['lng'] = float(lng) except ValueError: return Response(status=status.HTTP_400_BAD_REQUEST) # Network or SiDS submission? if request.data.get('satnogs_network'): data['app_source'] = 'network' else: data['app_source'] = 'sids' # Create file out of frame string frame = ContentFile(request.data.get('frame'), name='sids') data['payload_frame'] = frame # Create observer qth = gridsquare(data['lat'], data['lng']) observer = '{0}-{1}'.format(data['station'], qth) data['observer'] = observer serializer = serializers.SidsSerializer(data=data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) # Run task to decode the current frame decode_current_frame.delay(satellite.satellite_identifier.sat_id, serializer.instance.pk) # Run task to publish the current frame via ZeroMQ if settings.ZEROMQ_ENABLE: publish_current_frame.delay( request.data.get('timestamp'), request.data.get('frame'), observer, { 'norad_id': norad_id, 'observation_id': observation_id, 'station_id': station_id } ) return Response(status=status.HTTP_201_CREATED) @extend_schema_view( list=extend_schema( parameters=[ OpenApiParameter( 'network_obs_id', OpenApiTypes.INT64, required=False, description='Given a SatNOGS Network observation ID, this will return any \ artifacts files associated with the observation.' ), ], ), retrieve=extend_schema( parameters=[ OpenApiParameter( 'id', OpenApiTypes.URI, OpenApiParameter.PATH, description='The ID for the requested artifact entry in DB' ), ], ), ) class ArtifactViewSet( # pylint: disable=R0901 mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): """ Artifacts are file-formatted objects collected from a satellite observation. """ queryset = Artifact.objects.all() filterset_class = filters.ArtifactViewFilter permission_classes = [IsAuthenticatedOrOptions] parser_classes = (FormParser, MultiPartParser) pagination_class = pagination.LinkedHeaderPageNumberPagination def get_serializer_class(self): """Returns the right serializer depending on http method that is used""" if self.action == 'create': return serializers.NewArtifactSerializer return serializers.ArtifactSerializer def create(self, request, *args, **kwargs): """ Creates observation artifact from an [HDF5 formatted file][hdf5ref] * Requires session or key authentication to create an artifact [hdf5ref]: https://en.wikipedia.org/wiki/Hierarchical_Data_Format """ serializer = self.get_serializer(data=request.data) try: if serializer.is_valid(): data = serializer.save() http_response = {} http_response['id'] = data.id response = Response(http_response, status=status.HTTP_200_OK) else: data = serializer.errors response = Response(data, status=status.HTTP_400_BAD_REQUEST) except (ValidationError, ValueError, OSError) as error: response = Response(str(error), status=status.HTTP_400_BAD_REQUEST) return response