I have been programming an API using Django and djangorestframework for deployment in Google App Engine. The API is basically a package registry, so you can create, update, get, and delete packages with the API.
All the endpoints seem to work except for one. The only endpoint that doesn't work is one that display a paginated list of all packages in the online registry.
All the endpoints are working, but for some reason, when I hit the specific endpoint '/packages/', GCP gives me the error
400. That’s an error.
Your client has issued a malformed or illegal request. That’s all we know
When I run the application locally on my computer, all the endpoints work perfectly. The application only stops working for that specific route when I deploy it on Google App Engine.The API payload should be:
[ { "Name": "*" } ]
I am completely lost on this one and would appreciate any help.
VIEWS.py
import django.db.utils
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.core.paginator import Paginator, EmptyPage
import registry.models
from .serializers import *
from .models import *
# Create your views here.
@api_view(['GET'])
def apiOverview(request):
api_urls = {
'List': '/package-list/',
'Create': '/package-create/',
'Update': '/package-update/',
'Delete': '/package-delete/',
'Rate': '/package-rate/',
'Download': '/package-download/'
}
return Response(api_urls)
@api_view(['GET'])
def packages_middleware(request):
print(request)
print(type(request))
print(request.data)
print(type(request.data))
# determine offset query parameter
offset = request.GET.get('Offset')
if offset is None:
offset = 0
else:
offset = int(offset)
# capturing request body
response = []
queries = request.data
if len(queries) < 1:
return Response({'message: empty request body array'}, status=400)
else:
if len(queries) == 1 and queries[0]['Name'] == '*':
response = list(PackageMetadata.objects.all().values())
else:
for query in queries:
if 'Name' in query.keys() and 'Version' in query.keys():
for x in list(PackageMetadata.objects.filter(Name__icontains=query['Name']).filter(
Version__contains=query['Version']).values()):
response.append(x)
else:
response.append({
'message': 'query {q} is missing at least one attribute - remember to check spelling and capitalization'.format(q=query)
})
paginator = Paginator(response, 10)
try:
return Response(paginator.page(offset 1).object_list, headers={'Offset': offset 1})
except EmptyPage:
return Response(paginator.page(1).object_list, headers={'Offset': 2})
@api_view(['GET', 'PUT', 'DELETE'])
def package_middleware(request, pk):
try:
package = Package.objects.get(Metadata__ID__exact=str(pk))
if request.method == 'GET':
# CHECK THAT CONTENT FIELD IS SET
serializer = PackageSerializer(package, many=False)
return Response(serializer.data, status=200)
elif request.method == 'PUT':
payload = request.data
if 'Metadata' in payload and 'Data' in payload:
if payload['Metadata'] != PackageMetadataSerializer(package.Metadata, many=False).data:
return Response({"message": "name, version and ID must match"}, status=400)
else:
# CHECK THAT ONLY ONE DATA FIELD IS SET
serializer_data = PackageDataSerializer(data=payload['Data'], many=False)
if serializer_data.is_valid(raise_exception=True):
try:
serializer_data.update(instance=package.Data, validated_data=serializer_data.validated_data)
except django.db.utils.IntegrityError:
return Response(
{"message": "both Content and URL must be included in query, but exactly one can be set"},
status=400)
return Response(status=200)
else:
return Response(
{"message": "incorrect request body schema - remember to check spelling and capitalization"},
status=400)
else:
package.Metadata.delete()
package.Data.delete()
package.delete()
return Response({"message": "package deleted"}, status=200)
except registry.models.Package.DoesNotExist:
return Response({"message": "package not found"}, status=400)
@api_view(['POST'])
def create_package_middleware(request):
payload = request.data
print(payload)
if 'Metadata' in payload and 'Data' in payload:
serializer_metadata = PackageMetadataSerializer(data=payload['Metadata'], many=False)
serializer_data = PackageDataSerializer(data=payload['Data'], many=False)
if serializer_data.is_valid(raise_exception=True) and serializer_metadata.is_valid(raise_exception=True):
try:
metadata = PackageMetadata.objects.create(ID=serializer_metadata.data.get('ID'),
Name=serializer_metadata.data.get('Name'),
Version=serializer_metadata.data.get('Version'))
except django.db.utils.IntegrityError:
return Response({"message": "duplicate key-value (Name, Version) violates uniqueness constraint"},
status=403)
try:
data = PackageData.objects.create(Content=serializer_data.data.get('Content'),
URL=serializer_data.data.get('URL'))
except django.db.utils.IntegrityError:
metadata.delete()
return Response(
{"message": "both Content and URL must be included in query, but exactly one can be set"},
status=400)
Package.objects.create(Data=data, Metadata=metadata)
serializer_metadata = PackageMetadataSerializer(metadata, many=False)
return Response(serializer_metadata.data, status=200)
else:
return Response({"message": "incorrect request body schema - remember to check spelling and capitalization"},
status=400)
@api_view(['DELETE'])
def byName_middleware(request, name):
if name == '*':
return Response({"message": "query name reserved"}, status=400)
querySet = Package.objects.filter(Metadata__Name__iexact=name)
if len(querySet) == 0:
return Response({"message": "package not found"})
else:
for package in querySet:
package.Metadata.delete()
package.Data.delete()
package.delete()
return Response(status=200)
URLS.py
from django.urls import path, include
from . import views
urlpatterns = [
path('', views.apiOverview),
path('packages/', views.packages_middleware, name='packages_middleware'),
path('package/<str:pk>', views.package_middleware, name='package'),
path('package/', views.create_package_middleware, name='create'),
path('package/byName/<str:name>', views.byName_middleware, name='byName')
]
MODELS.py
from django.db import models
# Create your models here.
class PackageData(models.Model):
Content = models.TextField(blank=True, null=True) # actual zip file
URL = models.CharField(max_length=500, blank=True, null=True) # url of package
# class Meta:
# constraints = [
# models.CheckConstraint(
# name="%(app_label)s_%(class)s_content_or_url",
# check=models.Q(Content__isnull=True, URL__isnull=False) | models.Q(Content__isnull=False, URL__isnull=True)
# )
# ]
class PackageMetadata(models.Model):
class Meta:
constraints = [
models.UniqueConstraint(fields=['Name', 'Version'], name='unique_package')
]
ID = models.CharField(primary_key=True, max_length=50)
Name = models.CharField(max_length=50)
Version = models.CharField(max_length=50)
class Package(models.Model):
Data = models.ForeignKey(PackageData, on_delete=models.CASCADE)
Metadata = models.ForeignKey(PackageMetadata, on_delete=models.CASCADE)
class PackageRating(models.Model):
BusFactor = models.DecimalField(max_digits=10, decimal_places=9)
Correctness = models.DecimalField(max_digits=10, decimal_places=9)
GoodPinningPractice = models.DecimalField(max_digits=10, decimal_places=9)
LicenseScore = models.DecimalField(max_digits=10, decimal_places=9)
RampUp = models.DecimalField(max_digits=10, decimal_places=9)
ResponsiveMaintainer = models.DecimalField(max_digits=10, decimal_places=9)
class PackageQuery(models.Model):
Name = models.CharField(max_length=50)
Version = models.CharField(max_length=50)
CodePudding user response:
You are making a GET
call to /packages/
which is handled by the route definition packages_middleware(request)
.
In that function you have queries = request.data
and the rest of your logic is dependent on queries
. This document says request.data
is for POST, PATCH, PUT. If that is correct, then that seems like where your error lies.
You can put a print statement just before and after that line and see if your App prints the statement after. That will help you confirm if that is the issue.
You should also print the contents of queries
. Since you're not actually sending a body with your GET request, it is possible that the rest of your logic fails because the value of queries
is not what you're expecting.