I'm struggling to figure out how to implement the Hyperlinked relationships for non-model querysets. I have a viewset:
class GGGViewSet(viewsets.ViewSet):
def list(self, request):
serializer_class = manufacture_serializer(ar)
serializer = serializer_class(
instance = sample.values(), many=True
)
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
anobject = sample[pk]
except KeyError:
return Response(status=status.HTTP_404_NOT_FOUND)
except ValueError:
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer_class = manufacture_serializer(ar)
serializer = serializer_class(instance=anobject)
return Response(serializer.data)
I am trying to get the value resource at /data/trait/ to be rendered as a link, where:
trait-list
data/trait/
{
"value": 12334,
"another_value": 45672,
}
trait-detail
data/trait/value/
{
"value":12334
}
Attempted:
url = serializers.HyperlinkedIdentityField(view_name='trait-list')
Error: AttributeError at /asvo/data/trait/ 'AObj' object has no attribute 'pk'.
Any ideas on the best way to approach this would be appreciated. :)
You were probably quite close. Based on the information provided, here's something that demonstrates HyperlinkedIdentityField usage without relying on an actual Django model. I had to use my imagination when filling in the details of your architecture.
from rest_framework import routers
from rest_framework import serializers
from rest_framework import status
from rest_framework import viewsets
from rest_framework.response import Response
# This goes in the URL routing file
router = routers.DefaultRouter()
router.register(r'trait', GGGViewSet, base_name='trait')
urlpatterns = router.urls
# The "model"
class Thing(object):
def __init__(self, pk, value, another_value):
self.pk = pk
self.value = value
self.another_value = another_value
# The "queryset"
sample = {
'1': Thing(1, 12334, 45672),
'2': Thing(2, 12335, 45673),
'3': Thing(3, 12336, 45674)
}
# The serializer
class manufacture_serializer(serializers.Serializer):
pk = serializers.HyperlinkedIdentityField(
view_name='trait-detail', read_only=True)
value = serializers.IntegerField()
another_value = serializers.IntegerField()
class Meta:
fields = ['pk', 'value', 'another_value']
# The view
class GGGViewSet(viewsets.ViewSet):
def list(self, request):
serializer = manufacture_serializer(
instance=sample.values(), many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
anobject = sample[pk]
except KeyError:
return Response(status=status.HTTP_404_NOT_FOUND)
except ValueError:
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer = manufacture_serializer(
instance=anobject, context={'request': request})
return Response(serializer.data)
I did not fully understand the second half of the question regarding the data/trait/ and data/trait/value/, but hopefully this is enough to further you along.
Cheers!
Related
I have a API view in place where I first want to create a new user (already working) and second I want to return the new created user object using my UserSerializer (Not working).
views.py
#api_view(['POST'])
#permission_classes([AllowAny])
def user_create(request):
exception_handler = UserUnavailable
success_handler = UserCreated
if request.method == 'POST':
creation_serializer = CreateUserSerializer(data=request.data)
try:
if creation_serializer.is_valid(raise_exception=True):
creation_serializer.save()
user_serializer = UserSerializer(data=creation_serializer.instance.id)
if user_serializer.is_valid():
return JsonResponse({"status_code": success_handler.status_code,
"default_detail": success_handler.default_detail,
"default_code": success_handler.default_code,
"new_user": user_serializer,
}, safe=False)
except APIException:
return JsonResponse({"status_code": exception_handler.status_code,
"default_detail": exception_handler.default_detail,
"default_code": exception_handler.default_code
}, safe=False)
I can confirm that creation_serializer.instance.id contains the new users id.
serializers.py
class UserSerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
model = get_user_model()
fields = ('id', 'user')
read_only_fields = ('id', 'user')
I can also confirm that
if user_serializer.is_valid()
does not validate for some reason. Any ideas
Thanks in advance
To initialize a serializer with user instance instead of
user_serializer = UserSerializer(data=creation_serializer.instance.id)
You should write something like this:
user_serializer = UserSerializer(instance=creation_serializer.instance)
instead of
Also, you don't have to validate the object that has already been saved.
And create a response you should like this:
return JsonResponse({
"status_code": success_handler.status_code,
"default_detail": success_handler.default_detail,
"default_code": success_handler.default_code,
"new_user": user_serializer.data,
}, safe=False)
But using api_view is now a bad thing. It's much better to use ModelViewSet. You can use something like this:
class UserViewSet(ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
def get_serializer_class(self, *args, **kwargs):
if self.action == 'create':
return CreateUserSerializer
return super().get_serializer_class(*args, **kwargs)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response_serializer = self.serializer_class(instance=serializer.instance)
return Response(response_serializer.data, status=status.HTTP_201_CREATED, headers=headers)
And I'm sorry, I haven't tested it, there might be typos.
You can read about ModelViewsets more here https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
I am trying to access a specific comment using the retrieve method in Django View sets. I am using a default router in order to route my urls. I am able to list all comments at api/posts/, but am unable to get a single comment at api/posts/1. I am getting a type error: Field.init() got an unexpected keyword argument 'pk' when trying to access the URL. Any ideas as to why?
urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from posts.views import PostsViewSet, CommentsViewSet
router = DefaultRouter()
router.register(r"comments", CommentsViewSet, basename='comments')
urlpatterns = [
path("", include(router.urls)),
]
views.py
class CommentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(label='commentID')
comment = serializer.CharField()
created_at = serializers.DateTimeField(required=False)
updated_at = serializers.DateTimeField(required=False)
posts = serializers.SerializerMethodField()
class Meta:
model = Comments
# fields = "__all__"
fields = ['id', 'comment','created_at', 'updated_at', 'posts']
def to_representation(self, instance: Comment) -> dict:
'''Pass for now'''
ret = super().to_representation(instance)
return ret
def get_queryset(self) -> QuerySet:
qs = Comment.objects.all()
return qs
def create(self, validated_data: dict) -> Comment:
return Comment.objects.create(**validated_data)
def update(self, instance: Comment, validated_data: dict) -> Comment:
'''Pass post-validation errors silently'''
for field in validated_data:
setattr(instance, field, validated_data.get(
field, getattr(instance, field)))
instance.save()
return instance
class CommentViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Comment.objects.all()
result = CommentSerializer(queryset, many=True)
if result:
return Response(result.data)
else:
return Response(data=result.data, status=200)
def retrieve(self, request, pk=None):
queryset = Comment.objects.all()
condition = get_object_or_404(queryset, pk=pk)
result = CommentSerializer(queryset, pk=pk)
print(result)
return Response(result.data)
First you are not using the condition object in serializer.
Second drf default pk field name is id not pk and no need to pass pk during serialization process.Below is updated retrieve method kindly check the same.
def retrieve(self, request, pk=None):
# queryset = Comment.objects.all() #no need as you are using get_object_or_404 to fetch single object
condition = get_object_or_404(Comment, id=pk) #replaced pk by id here
result = StudentSerializer(condition) #used condition in serializer
print(result)
return Response(result.data)
I'm trying to use Django Reversion with my django-rest, but i still confuse about it or i just couldn't what i want in Document.
Here what i tried
Settings
INSTALLED_APPS = [
....
'reversion'
]
MIDDLEWARE = [
'reversion.middleware.RevisionMiddleware'
]
In Model
#reversion.register()
class History(models.Model):
pass
In ModelViewset
from reversion.views import RevisionMixin
class HistoryViewset(RevisionMixin, viewsets.ModelViewSet):
queryset = History.objects.all()
serializer_class = HistorySerializer
filter_backends = (filters.DjangoFilterBackend,)
# if pagination page = none, it will return all page
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
if 'page' in request.query_params:
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Then i try to update My Model and try to GET request from api endpoint, i got null Value.
What do i Missing???
I'll appreciate of all ur Help. Thanks...
I need to update categories in many Article in one request.
In ArticleViewSet I have:
def get_serializer_class(self):
if self.action in ['partial_update', 'update']:
return ArticlePostSerializer
return ArticleSerializer
So ArticlePostSerializer need to be changed.
This is my serializers code:
class ArticleShortCategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = 'id', 'name'
class ArticleSerializer(serializers.ModelSerializer):
categories = serializers.SerializerMethodField()
def get_categories(self, obj):
return ArticleShortCategorySerializer(obj.categories, many=True).data
class Meta:
model = Article
read_only_fields = 'id'
fields = ('categories', 'text') + read_only_fields
class ArticlePostSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = 'id', 'categories', 'text'
I tried to add:
class ArticlePostListSerializer(serializers.ListSerializer):
and
class Meta:
list_serializer_class = ArticlePostListSerializer
But it doen't work.
How to change this code to do multiple update.
My json request
{
[id: 90, categories: [10,12,14]],
[id: 93, categories: [10,12,14]],
[id: 95, categories: [10,12,14]]
}
Here is sample of CreateMixins OR UpdateMixins you requested.
======================= VIEW ================================
class OrderCreate(mixins.CreateModelMixin,viewsets.GenericViewSet):
pagination_class = None
def get_queryset(self):
return []
def get_serializer_class(self):
return serializers.OrderSerializer
======================= Serializer =============================
class OrderDetailSerializer(serializers.ModelSerializer):
class Meta:
model = crm_models.OrderDetail
fields = (
'product',
'quantity',
'rate_per_unit',
'order_quantity'
)
class OrderSerializer(serializers.ModelSerializer):
order_details = OrderDetailSerializer(many = True)
class Meta:
model = crm_models.OrderMaster
fields = (
'order',
'invoice_number',
'client',
'beat_check',
'target_customer',
'order_editor_client_employee',
'order_marked',
'order_saved',
'edit_marked',
'edit_saved',
'adhoc',
'order_details'
)
def create(self, validated_data,*args,**kwargs):
ordersdetails_data = validated_data.pop('order_details')
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
validated_data['client'] = user.client
validated_data['order_editor_client_employee'] = user
validated_data['adhoc'] = validated_data['adhoc'] if 'adhoc' in validated_data else False
orderObj = super(OrderSerializer, self).create(validated_data,*args,**kwargs)
orderdetails = []
for details in ordersdetails_data:
orderdetails.append(crm_models.OrderDetail(
product= details['product'],
quantity = details['quantity'],
rate_per_unit = details['rate_per_unit'],
order_quantity = details['order_quantity'],
order = orderObj
))
crm_models.OrderDetail.objects.bulk_create(orderdetails)
return orderObj
In Update view function name would be changed to update, you can find more documentation http://www.django-rest-framework.org/api-guide/generic-views/#createmodelmixin
I found K. Moe's answer to this question: Django Rest Framework POST Update if existing or create much easier to understand and implement. You only need to add a create method in the serializer and use mixins.CreateModelMixin, generics.GenericAPIView in the view. Then you can use a POST request, instead of PUT/PATCH. It allows to create AND update data stored in your database. My code for the view:
class ZipCodeList(mixins.CreateModelMixin, generics.GenericAPIView):
def post(self, request, format=None):
is_many = isinstance(request.data, list)
if is_many:
serializer = ZipCodeSerializer(data=request.data, many=True)
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)
else:
serializer = ZipCodeSerializer(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)
#Greg Holst, why so much duplication? Why not:
class ZipCodeList(mixins.CreateModelMixin, generics.GenericAPIView):
def post(self, request, format=None):
serializer = ZipCodeSerializer(data=request.data, many=isinstance(request.data, list))
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)
Anyway, that only worked for me for creating new objects, didn't work to create-or-update in one sweep (it kept telling me these objects already exist), so this is what I did:
class ContributorSyncListAPIView(ListAPIView):
permission_classes = (isSuperUserPermission,)
allowed_methods = ("GET", "PUT")
lookup_field = "airtable_id"
serializer_class = ContributorSyncSerializer # Doesn't get used in Put, just in Get.
model = Contributor
def get_queryset(self):
return self.model.objects.all()
def put(self, request, format=None):
objects = list(request.data)
# objects is a list of OrderedDicts
try:
for o in objects:
try:
instance = self.model.objects.get(
**{self.lookup_field: o.get(self.lookup_field)}
)
for key, value in o.items():
setattr(instance, key, value)
except self.model.DoesNotExist:
instance = self.model(**o)
instance.save()
return Response(objects, status=status.HTTP_200_OK)
except Exception as e:
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
Note my code above is very light on validation just because it was for a process of syncing models from one environment to another by a superuser, different environments with an identical codebase; so the assumption was the data had already been validated when the data was entered into the first environment. For any other purpose you'd want to validate more. But this is what I had to do to handle a list of objects that may need to be created or updating, on an object-by-object basis.
I am trying to return custom json with get_queryset but always get 404 error in response.
class TestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Test.objects.all()
serializer_class = TestSerializer
def get_queryset(self):
if self.request.method == "GET":
content = {'user_count': '2'}
return HttpResponse(json.dumps(content), content_type='application/json')
If I delete everything starting from def I'll got correct response with standard json data. What I am doing wrong?
If you don't need a ModelViewSet and just want custom JSON on a GET request
You can also use an APIView, which doesn't require a model
class MyOwnView(APIView):
def get(self, request):
return Response({'some': 'data'})
and
urlpatterns = [
url(r'^my-own-view/$', MyOwnView.as_view()),
]
With a ModelViewSet
You've put the custom JSON into get_queryset, that's wrong. If you want to use a ModelViewSet, this by itself should be enough:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
This ModelViewSet comes with default implementations for .list(), .retrieve(), .create(), .update(), and .destroy(). Which are available for you to override (customize) as needed
Returning custom JSON from .retrieve() and/or .list() in ModelViewSet
E.g. to override .retrieve() to return custom view when retrieving a single object. We can have a look at the default implementation which looks like this:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
So as an example to return custom JSON:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
def retrieve(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
def list(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
There are 2 ways to custom the response in Class-based views with ModelViewSet
Solution 1: custom in views.py
class StoryViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.AllowAny,)
queryset = Story.objects.all()
serializer_class = StorySerializer
def retrieve(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).retrieve(request)
return Response({'key': 'single value'})
def list(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).list(request)
return Response({'key': 'list value'})
Solution 2: custom in serializers.py (I recommend this solution)
class StorySerializer(serializers.ModelSerializer):
class Meta:
model = Story
fields = "__all__"
def to_representation(self, instance):
ret = super(StorySerializer, self).to_representation(instance)
# check the request is list view or detail view
is_list_view = isinstance(self.instance, list)
extra_ret = {'key': 'list value'} if is_list_view else {'key': 'single value'}
ret.update(extra_ret)
return ret