I am using Django rest framework for my api. In my views.py file i am using Viewset.ModelViewset.
class SchemaViewSet(viewsets.ModelViewSet):
queryset = models.Schema.objects.all()
serializer_class = serializers.SchemaSerializer
My URL for this is : http://127.0.0.1:7000/api/schema/
THis is giving me GET and POST option. The response is something like this:
{
"id": 1,
"name": "yatharth",
"version": "1.1"
},
To Delete/Put/Patch i have to pass id which is 1 like this: http://127.0.0.1:7000/api/schema/1/.
Can i do this by name like this: http://127.0.0.1:7000/api/schema/yatharth/ instead of id.
My model.py (I can Set name to unique = True)
class Schema(models.Model):
"""Database model for Schema """
name= models.TextField()
version = models.TextField()
def __str__(self):
return self.name
I need id parameter also, so removing it isn't an option but instead of making query by id, i need to make it by name (both are unique)
what i found on django rest framework documentation
def retrieve(self, request, pk=None):
queryset = User.objects.all()
user = get_object_or_404(queryset, pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data)
But don't know how to change primary key?
You can set do this by setting lookup_field = 'name' in your SchemaViewSet.
eg
class SchemaViewSet(viewsets.ModelViewSet):
queryset = models.Schema.objects.all()
serializer_class = serializers.SchemaSerializer
lookup_field = 'name'
Related
I have database which consists of configuration of application. Application can have various configuration keys and basically I don't have information about how much keys I have and name of keys are not known too. I need to have response as {key: value, }. But I have response {key_field: key, value_field: value}. What should I do basically in this case? Does using MongoDB instead of PostgreSQL or SQlite help me? Or any other ideas?
Model looks like:
class Service(models.Model):
name = models.TextField()
version = models.IntegerField()
class ServiceKey(models.Model):
service = models.ForeignKey(
Service,
on_delete=models.CASCADE
)
service_key = models.TextField()
service_value = models.TextField()
views:
#api_view(['GET', ])
def hello(request):
name = request.query_params.get('service')
try:
service = Service.objects.get(name=name)
service_key_instance = ServiceKey.objects.filter(service=service)
serializer = KeySerializer(instance=service_key_instance, many=True)
return Response(data=serializer.data, status=status.HTTP_200_OK)
except:
Response(data='record not found', status=status.HTTP_400_BAD_REQUEST)
on your serializer, you should overwrite to_representation method for example:
class ServiceSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = [
"name",
"version",
]
def to_representation(self, instance):
response = super().to_representation(instance)
response["keys"] = {
key.service_key: key.service_value for key in instance.service_key_set.all()
}
return response
and just use this serializer in your API view
Using Django REST framework I created an url which maps to a page with a JSON file containing all the objects in my database.
I want to do the same but instead of showing all the objects I want only the objects that match a specific category (category is an attribute in my model).
I have urls that show a JSON files with a single object in it (using the pk attribute) but when I try to do the same thing with category instead of pk I get a MultipleObjectsReturned error.
I'm just sperimenting with the REST framework, I tried using different views and class based views solving nothing.
Any hint or suggestion is really appreciated thanks.
# models.py
class Hardware(models.Model):
name = models.CharField(max_length=25)
category = models.CharField(choices=CATEGORY_CHOICES, max_length=2)
def get_api_url(self):
return api_reverse("category-api-postings:post-rud", kwargs={'category': self.category})
#views.py
class HardwareListView(generics.ListCreateAPIView):
pass
lookup_field = 'pk'
serializer_class = HardwareSerializer
def get_queryset(self):
query = self.request.GET.get("q")
qs = Hardware.objects.all()
if query is not None:
qs = qs.filter(Q(title__icontains=query) | Q(content__icontains=query)).distinct()
return qs
class HardwareRudView(generics.RetrieveUpdateDestroyAPIView):
pass
lookup_field = 'category'
serializer_class = HardwareSerializer
def get_queryset(self):
return Hardware.objects.all()
#urls.py
app_name = 'category-api-postings'
urlpatterns = [
path('', exercise_view),
path('list-api/', HardwareListView.as_view(), name='all'),
path('list-api/<str:category>/', HardwareRudView.as_view(), name='post-rud')
#serializer.py
class HardwareSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Hardware
fields = [
'url',
'pk',
'name',
'category'
]
read_only_fields = ['user']
def get_url(self, obj):
return obj.get_api_url()
As I understand, you want url /list-api/HD/ to return all Hardware objects from given category. For that HardwareRudView must inherit ListAPIView, not RetrieveUpdateDestroyAPIView. For example, like this:
class HardwareRudView(generics.ListAPIView):
serializer_class = HardwareSerializer
def get_queryset(self):
category = self.kwargs['category']
return Hardware.objects.filter(category=category)
See related docs: https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-url
This is the first time I'm working with DRF.
My models:
class ServiceCategory(models.Model):
category = models.CharField(max_length=24)
class Service(models.Model):
service = models.CharField(max_length=24)
category = models.ForeignKey('ServiceCategory')
Their serializers:
class ServiceCategorySerializer(serializers.ModelSerializer):
class Meta:
model = ServiceCategory
fields = ('id', 'category')
class ServiceSerializer(serializers.ModelSerializer):
category = ServiceCategorySerializer()
class Meta:
model = Service
fields = ('service', 'category')
def create(self, data):
return Service.objects.create(**data)
And the view:
elif request.method == 'POST':
serializer = ServiceSerializer(data=request.data)
print(serializer.initial_data) # To debug the contents of the request
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)
Initially, before I added the nested category to the ServiceSerializer, I had no problem creating new Services. The print(serializer.initial_data) outputs <QueryDict: {'category': ['1'], 'service': ['EC2']}> so obviously I'm supplying the category to the request but I'm getting "category" : ["This field is required"] errors.
So I'm thinking the problem might be with my create(self, data) method in the ServiceSerializer but I'm unable to put a finger on what exactly is wrong with it.
What have I missed?
UPDATE
Without the ServiceCategorySerializer in the ServiceSerializer, and the view being:
elif request.method == 'POST':
serializer = ServiceSerializer(data=request.data)
print(serializer.initial_data) # for debugging
if serializer.is_valid():
print(serializer.data) # for debugging
serializer.initial_data returns <QueryDict: {'category': ['1'], 'service': ['EC2']}>
and
serializer.data returns {'service': 'EC2', 'category': 1} so I assume the contents of serializer.data are what will get passed to the create() method of the ServiceSerializer. By itself, it works, but when I include the ServiceCategorySerializer inside it, the POST doesn't go through and I get the same annoying "category" : ["This field is required"]
I've been stuck with this for over 6 hours now. What is going on???
I have a full working example - of what you wanna achieve - using just information that I found in this thread:
Models:
from __future__ import unicode_literals
from django.db import models
class ServiceCategory(models.Model):
category = models.CharField(max_length=24)
class Service(models.Model):
service = models.CharField(max_length=24)
category = models.ForeignKey('ServiceCategory')
Serializers:
from rest_framework import serializers
from nestedd.models import ServiceCategory, Service
class ServiceCategorySerializer(serializers.ModelSerializer):
class Meta:
model = ServiceCategory
fields = ('id', 'category')
class ServiceSerializer(serializers.ModelSerializer):
category = ServiceCategorySerializer()
class Meta:
model = Service
fields = ('service', 'category')
def create(self, validated_data):
category_data = validated_data.pop('category')
# 'created' will be True if no existing category matches
category, created = ServiceCategory.objects.get_or_create(**category_data)
return Service.objects.create(category=category, **validated_data)
Views:
# Create your views here.
from rest_framework import viewsets
from nestedd.models import Service
from nestedd.serializers import ServiceSerializer
class ServiceViewSet(viewsets.ModelViewSet):
queryset = Service.objects.all()
serializer_class = ServiceSerializer
urls:
from rest_framework.routers import DefaultRouter
from nestedd.views import ServiceViewSet
router = DefaultRouter()
router.register(r'nested', ServiceViewSet, base_name='service')
urlpatterns = router.urls
app urls:
url(r'^api/v2/', include('nestedd.urls')),
And this how my postman look likes:
THE PROBLEM - POST DATA FORMAT
Probably you made a wrong POST query - if you want to use nested serializer, like this:
category = ServiceCategorySerializer()
In some other serializer, you must know that first field name is attached to the parent serializer, eg.:
{
"service_name": "test",
"category": ...
}
And what should be placed in category field? Well - an object, because you tell that this field is another serializer, if object then:
{
"service_name": "test",
"category": {
"category": "some_category"
}
}
And in this object you specify the fields for the model which is described by inner serializer, so basically, when you pass only the "id" -> it obvious that ServiceCategory cannot be created, beacause the category field on model ServiceCategory - is required.
ANOTHER NOTE: EXISITING VS. NON EXISITNG CATEGORY
You will have problems with handling existing/non-existing category;
Basically you should make category field unique on ServiceCategory, and in post on ServiceViewSet - check if category exists (if so take it and assign to the Service object - if no - create a category) - in this scenario you will not need to pass category id each time. And handle it when id - does not exists.
When POSTing a service, the category field must contain a PK, i.e. an integer. Your category field in serializer.initial_data contains a list with a string.
BTW1: your service field also has a list when your model expects a string (CharField). This might also be a problem.
BTW2: No need to override your serializer's create in your case.
As stated in the docs, you should implement the create() method in a slightly different way, saving the category first if it doesn't exist yet, and then passing it to the Service.objects.create() function, like this (untested):
def create(self, validated_data):
category_data = validated_data.pop('category')
# 'created' will be True if no existing category matches
category, created = ServiceCategory.objects.get_or_create(**category_data)
return Service.objects.create(category=category, **validated_data)
I have the following json I'm trying to post -
{
"title": "test",
"description": "testing desc",
"username": "admin",
"password": "Welcome1",
"owner": 4
}
The ModelViewSet is below -
class PasswordViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = Password.objects.all()
serializer_class = PasswordSerializer
filter_class = PasswordFilter
def list(self, request):
my_passwords = Password.objects.filter(owner=request.user)
page = self.paginate_queryset(my_passwords)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = PasswordSerializer(my_passwords, many=True)
return Response(serializer.data)
If I remove the def list I can do a post just fine. The problem is trying to post with that def in there. it returns that all my fields are required. Why is it not passing the data?
This is my serializer for reference -
class PasswordSerializer(serializers.ModelSerializer):
class Meta:
model=Password
edit - no idea what's going wrong testing some more here and now it's not even working with the def list removed.
model -
class Password(models.Model):
title = models.CharField(max_length=100)
description = models.CharField(max_length=1024)
username = models.CharField(max_length=50)
password = models.CharField(max_length=200)
owner = models.ForeignKey('MyUser', related_name='MyUser_owner')
The response is telling me all the fields in the model are required so to me on post the request.data is empty even though the json should be it.
In Django REST Framework API, list of database table records are not getting updated until the API restart or any code change in python files like model, serializer or view. I've tried the transaction commit but it didn't worked. Below is my view :
class ServiceViewSet(viewsets.ModelViewSet):
#authentication_classes = APIAuthentication,
queryset = Service.objects.all()
serializer_class = ServiceSerializer
def get_queryset(self):
queryset = self.queryset
parent_id = self.request.QUERY_PARAMS.get('parent_id', None)
if parent_id is not None:
queryset = queryset.filter(parent_id=parent_id)
return queryset
# Make Service readable only
def update(self, request, *args, **kwargs):
return Response(status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, *args, **kwargs):
return Response(status=status.HTTP_400_BAD_REQUEST)
Serializer looks like this :
class ServiceSerializer(serializers.ModelSerializer):
class Meta:
model = Service
fields = ('id', 'category_name', 'parent_id')
read_only_fields = ('category_name', 'parent_id')
and model looks like this :
class Service(models.Model):
class Meta:
db_table = 'service_category'
app_label = 'api'
category_name = models.CharField(max_length=100)
parent_id = models.IntegerField(default=0)
def __unicode__(self):
return '{"id":%d,"category_name":"%s"}' %(self.id,self.category_name)
This problem is occuring only with this service, rest of the APIs working perfectly fine. Any help will be appreciated.
Because you are setting up the queryset on self.queryset, which is a class attribute, it is being cached. This is why you are not getting an updated queryset for each request, and it's also why Django REST Framework calls .all() on querysets in the default get_queryset. By calling .all() on the queryset, it will no longer use the cached results and will force a new evaluation, which is what you are looking for.
class ServiceViewSet(viewsets.ModelViewSet):
queryset = Service.objects.all()
def get_queryset(self):
queryset = self.queryset.all()
parent_id = self.request.QUERY_PARAMS.get('parent_id', None)
if parent_id is not None:
queryset = queryset.filter(parent_id=parent_id)
return queryset