AttributeError while using Django Rest Framework with serializers - python

I am following a tutorial located here that uses Django Rest Framework, and I keep getting a weird error about a field.
I have the following model in my models.py
from django.db import models
class Task(models.Model):
completed = models.BooleanField(default=False)
title = models.CharField(max_length=100)
description = models.TextField()
Then my serializer in serializers.py
from rest_framework import serializers
from task.models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = ('title', 'description', 'completed')
and my views.py as follows:
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from task.models import Task
from api.serializers import TaskSerializer
#api_view(['GET', 'POST'])
def task_list(request):
"""
List all tasks, or create a new task
"""
if request.method == 'GET':
tasks = Task.objects.all()
serializer = TaskSerializer(tasks)
return Response(serializer.data)
elif request.method == 'POST':
serializer = TaskSerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
and my urls.py has this line:
url(r'^tasks/$', 'task_list', name='task_list'),
When I try accessing curl http://localhost:9000/api/tasks/, I keep getting the following error and I'm not sure what to make of it:
AttributeError at /api/tasks/
Got AttributeError when attempting to get a value for field `title` on serializer `TaskSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance.
Original exception text was: 'QuerySet' object has no attribute 'title'.
What I'm I missing?

Simple specify many=True when creating a serializer from queryset, TaskSerializer(tasks) will work only with one instance of Task:
tasks = Task.objects.all()
serializer = TaskSerializer(tasks, many=True)

The problem here is that you are trying to convert a Queryset(list) of entries into a single entry. The solution is something along these lines.
from rest_framework import serializers
class TaskListSerializer(serializers.ListSerializer):
child = TaskSerializer()
allow_null = True
many = True
Then
if request.method == 'GET':
tasks = Task.objects.all()
serializer = TaskListSerializer(tasks)
return Response(serializer.data)

Related

DRF + Djongo: How to access and work with ID from legacy database?

I set up a Django REST Framework (DRF) in combination with djongo, since I'm having a legacy database in MongoDB/Mongo express. Everything runs through a docker container, but I don't think that this is the issue. My model looks like this:
from django.db import models
## Event
class Event(models.Model):
ID = models.AutoField(primary_key=True)
date = models.DateField(default=None)
name = models.CharField(default=None, max_length=500)
locality = models.CharField(default=None, max_length=500)
class Meta:
ordering = ['ID']
db_table = 'event'
Next, my serializer is as follows:
from rest_framework import serializers
from .models import Event
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields= "__all__"
And the view looks like this:
from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .serializers import *
from .models import *
## Events
#api_view(['GET', 'POST'])
def event_list(request):
if request.method == 'GET':
events = Event.objects.all().order_by('ID')
event_serializer = EventSerializer(events, many=True)
return Response(event_serializer.data)
elif request.method == 'POST':
event_serializer = EventSerializer(data=request.data)
if event_serializer.is_valid():
event_serializer.save()
return Response(event_serializer.data, status=status.HTTP_201_CREATED)
return Response(event_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
As soon as I'm trying to post with
import requests
endpoint = "http://localhost:8080/event/"
get_response = requests.post(endpoint, {"date":"2022-06-01", "name":"Max Musterfrau", "locality":"North America"} )
I'm getting back the error message
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'ObjectId'
Following the documentation and other StackOverflow questions, in my model ID = models.AutoField(primary_key=True) should create the index ID as the primary key and auto-increments it. But at the same time, MongoDB creates its own _id and the actual ID from my legacy database doesn't auto-increment. I've tried both suggested solutions from here, but unfortunately it didn't change a thing. Therefore, my question is: How can I auto-increment the ID from my legacy database when POSTing an object? How do I create it automatically?
We need to create Separate serializer filed for _id = ObjectIdField like below
from bson import ObjectID
from bson.errors import InvalidID
class ObjectIdField(serializers.Field):
""" Serializer field for Djongo ObjectID fields """
def to_internal_value(self, data):
# Serialized value -> Database value
try:
return ObjectId(str(data)) # Get the ID, then build an ObjectID instance using it
except InvalidId:
raise serializers.ValidationError(
'`{}` is not a valid ObjectID'.format(data)
def to_representation(self, value):
# Database value -> Serialized value
if not ObjectId.is_valid(value): # User submitted ID's might not be properly structured
raise InvalidId
return smart_text(value)
class EventSerializer(ModelSerializer):
_id = ObjectIdField(read_only=True)
class Meta:
model = Event
fields = '__all__'

Django Attribute Error when using serializer

I would like to fetch the entire table. My model and serializer seems to be correct but I am getting the below error
Got AttributeError when attempting to get a value for field symbol on serializer CompanySerializer.
The serializer field might be named incorrectly and not match any attribute or key on the QuerySet instance.
Original exception text was: 'QuerySet' object has no attribute 'symbol'.
Below is my Model
models.py
from django.db import models
class Companies(models.Model):
symbol = models.CharField(max_length=100)
name = models.CharField(max_length=255)
isin = models.CharField(max_length=255)
serializers.py
from rest_framework import serializers
from .models import Companies
class CompanySerializer(serializers.ModelSerializer):
class Meta:
fields = ['symbol', 'name', 'isin',]
# fields = '__all__'
model = Companies
Below is my view
views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Companies
from .serializers import CompanySerializer
from django.core.serializers import json
class companiesView(APIView):
def get(self, request):
companies = Companies.objects.filter(id=1)
serializer = CompanySerializer(companies)
# json_serializer = json.Serializer()
# json_serialized = json_serializer.serialize(companies)
response = Response()
response.data = {
'named' : serializer.data,
}
return response
I am not sure what is causing this issue. Thanks in Advance.
companies is a queryset, so what you have is a list of companies. If you want your serializer to work with a list of objects, just add many=True:
companies = Companies.objects.filter(id=1)
serializer = CompanySerializer(companies, many=True)
# ^^^ Add this
Another way is to just get the company with id 1 using get instead of filter. In this case you don't have to add many=True since the serializer is working on a single object:
companies = Companies.objects.get(id=1)
# ^^^ Use get instead of filter
serializer = CompanySerializer(companies)

Django Swagger won't allow me to use POST method (no parameters shown)

I'm using djangorestframework together with drf-spectacular modules for a Django project, and I'm trying to build some basic API methods for my Project model. Its structure looks like this:
from django.db import models
# Create your models here.
class Project(models.Model):
title = models.CharField(max_length = 128)
description = models.TextField()
image = models.URLField()
date = models.DateTimeField(auto_now_add=True)
I also have a serializer for the model, looking like this:
from rest_framework import serializers
from api.models.Project import Project
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ['title', 'description', 'image', 'date']
Then, in views.py, I created two functions: project_list_view, which either lets you to GET all the Project objects from the database, or lets you POST a new object. And finally, project_detail_view, which lets you GET a Project object by typing in its pk (integer id). These are my two functions:
#api_view(['GET', 'POST'])
def project_list_view(request):
if request.method == 'GET':
projects = Project.objects.all()
serializer = ProjectSerializer(projects, many=True)
return Response(serializer.data)
elif request.method == "POST":
serializer = ProjectSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#api_view(['GET'])
def project_detail_view(request, pk):
if request.method == "GET":
try:
project = Project.objects.get(pk = pk)
serializer = ProjectSerializer(project, many = False)
return Response(serializer.data, status = status.HTTP_200_OK)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
The GET from project_list_view and project_detail_view work, but my problem lays in the POST method.
My Swagger is set to display its API Schema when accessing http://127.0.0.1:8000/docs/, and as I said, GET methods work properly, but when I'm trying to click on "Try it out" at the POST method, the fields are not displayed. I can only press "Execute" without actually being able to complete anything. After I click on "Execute", Swagger returns a 404 Bad Request response.
This is how POST looks like in Swagger:
My question is: Why won't Swagger display fields for each parameter of the model? Thank you.
Swagger Grabs the fields from a serializer_class variable.
I really recommend you change the format to Class-Based Views.
Something using mixins or generic class.
Your view could be like
class ProjectView(mixins.RetrieveModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = ProjectSerializer
queryset = Project.objects.all()
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
More on Mixins and Generic Views

Is there a way to grab specific "fields" from request.data sent to the Django REST framework API in a POST method

I've got a Project model, with a project_code field. When the API receives a POST request, the request.data will also contain a project_code. I then want to filter my Project model objects based on the project_code inside the request.data
Once I've linked to request.data project_code to the Project model's project_code field, I want to save my Ticket model object to the database. Inside my Ticket model, there is a field called project which is related with a ForeignKey to the Project model.
Thus in essence the project_code inside the POST request.data needs to be used in order to save my Ticket model to the database with the correct Project model foreign Key.
Here are my models:
from django.db import models
class Project(models.Model):
project_code = models.TextField(blank=True)
project_description = models.TextField(blank=True)
def __str__(self):
return self.project_code
class Ticket(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
ticket_url = models.TextField(blank=True)
time_submitted = models.DateField(blank=True, auto_now_add=True)
description = models.TextField(blank=True)
user = models.TextField(blank=True)
type = models.TextField(blank=True)
def __str__(self):
return self.description
Here are my serializers:
from rest_framework import serializers
from ticketing_app_api.models import Ticket, Project
class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = ['id', 'ticket_url', 'description', 'user', 'type']
And here are my views:
from ticketing_app_api.models import Ticket
from ticketing_app_api.serializers import TicketSerializer
from rest_framework import generics
from rest_framework.decorators import api_view
from rest_framework.response import Response
# from rest_framework.reverse import reverse
from rest_framework import status
#api_view(['GET', 'POST'])
def ticket_list(request):
"""
List all tickets, or creates a new ticket.
"""
if request.method == 'GET':
tickets = Ticket.objects.all()
serializer = TicketSerializer(tickets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = TicketSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class TicketDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
The cleaner approach would be to get the project_id when you create the project, and then just send it when creating the ticket. In this case, your TicketSerializer must also contain the project:
class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = ["id", "ticket_url", "description", "user", "type", "project"]
and when you send the post request, you have to specify which is the project:
{
"ticket_url": "http://ticket.com",
"project": 1
}
In case you must use the project_code, you can set the project when validating the data:
class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = ["id", "ticket_url", "description", "user", "type"]
def validate(self, attrs):
attrs["project"] = Project.objects.get(
project_code=self.initial_data.get("project_code")
)
return attrs

Django Rest Framework not showing form fields for PATCH/PUT request

I have a Django project that is using Django Rest Framework. For some reason, when I go to one of my endpoints (to make a PATCH/PUT request), I am not seeing any of the form fields in the browsable API. Here is the code for the resource:
models.py
from django.db import models
class Patient(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
diagnosis = models.CharField(max_length=200)
primary_doctor = models.ForeignKey('Doctor', related_name='patients', on_delete=models.CASCADE, null=True)
born_on = models.DateField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return "{0.first_name} {0.last_name}".format(self)
views.py
from rest_framework.views import APIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from rest_framework.response import Response
from rest_framework import status
from django.shortcuts import get_object_or_404
from ..models.patient import Patient
from ..serializers import PatientSerializer
class Patients(APIView):
def get(sef, request):
patients = Patient.objects.all()[:10]
serializer = PatientSerializer(patients, many=True)
return Response(serializer.data)
serializer_class = PatientSerializer
def post(self, request):
patient = PatientSerializer(data=request.data)
if patient.is_valid():
patient.save()
return Response(patient.data, status=status.HTTP_201_CREATED)
else:
return Response(patient.errors, status=status.HTTP_400_BAD_REQUEST)
class PatientDetail(APIView):
def get(self, request, pk):
patient = get_object_or_404(Patient, pk=pk)
serializer = PatientSerializer(patient)
return Response(serializer.data)
serializer_class = PatientSerializer
def patch(self, request, pk):
patient = get_object_or_404(Patient, pk=pk)
serializer = PatientSerializer(patient, data=request.data['patient'])
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
patient = get_object_or_404(Patient, pk)
patient.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
serializers.py
from rest_framework import serializers
from .models.patient import Patient
class PatientSerializer(serializers.ModelSerializer):
class Meta:
model = Patient
fields = ('id', 'first_name', 'last_name', 'diagnosis', 'born_on', 'primary_doctor', 'created_at', 'updated_at')
urls.py
from django.urls import path
from .views.doctor_views import Doctors, DoctorDetail
from .views.patient_views import Patients, PatientDetail
urlpatterns = [
path('doctors/', Doctors.as_view(), name='doctors'),
path('doctors/<int:pk>/', DoctorDetail.as_view(), name='doctor_detail'),
path('patients/', Patients.as_view(), name='patients'),
path('patients/<int:pk>/', PatientDetail.as_view(), name='patient_detail'),
]
This is what the browser looks like when I go to '/patients/3'. There are no form fields to fill out, only a content area for JSON. When I go to POST at '/patients', the form fields appear and I can POST fine.
Could anyone tell me why this might be happening?
If you change PatientDetail's patch method to put, the form should appear and function properly at the url to update a patient. You can use them somewhat interchangeably with DRF, to my understanding (No difference between PUT and PATCH in Django REST Framework). The difference lies in whether the serializer fields are required or not.
Though, they are technically different things at a low level - patch being a real 'partial update' (Use of PUT vs PATCH methods in REST API real life scenarios).
In the put method, you also should change
serializer = PatientSerializer(patient, data=request.data['patient'])
to
serializer = PatientSerializer(data=request.data)
Then, the endpoints should behave as expected.

Categories