partial update in django rest viewset(not modelviewset) - django

i am beginner in django and i was trying to learn work with djangorest viewset(not modelviewset)
and i cant find any resource to undrestand it.
i want to learn how to write a partial_update with viewset.
here is what i have tried:
models.py :
class UserIdentDocs(models.Model):
owner = models.ForeignKey(User,on_delete=models.CASCADE, related_name = "user_ident_Docs")
code = models.PositiveBigIntegerField(null=True)
img = models.ImageField(null=True)
video = models.FileField(null=True)
is_complete = models.BooleanField(default=False)
serializers.py:
class AdminUserIdentSerializer(serializers.ModelSerializer):
class Meta:
model = UserIdentDocs
fields = [
"owner",
"code",
"img",
"video",
"is_complete",
]
views.py:
from rest_framework.parsers import MultiPartParser, FormParser
class UserDocAdminViewSet(viewsets.ViewSet):
"""User docs admin view set"""
permission_classes = [AllowAny]
parser_classes = (MultiPartParser, FormParser)
serializer_class = AdminUserIdentSerializer
queryset = UserIdentDocs.objects.filter(is_complete=False)
def list(self, request):
serializer = self.serializer_class(self.queryset, many=True)
return Response(serializer.data)
def retrive(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object)
return Response(serializer.data)
def create(self, request ):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data,status=status.HTTP_201_CREATED)
def partial_update(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object,data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"detail":"item updated succesfuly"}, status=status.HTTP_205_RESET_CONTENT)
urls.py:
urlpatterns = [
...
# admin viewset for docs
# list of accounts with is_complete=False
path("admin/userdocs/", UserDocAdminViewSet.as_view({"get":"list", "post":"create"}), name="user-docs-list"),
path("admin/userdocs/<int:pk>", UserDocAdminViewSet.as_view({"get":"retrive","patch":"partial_update"}), name="user-docs-detail"),
]
i can create user in browsable api but when i want to use partial update i can't even see the fields in browsable api and i only see this :
media type: multipart/form-data
content:this is what i see

Related

How to assign multiple api views to single endpoint in django rest framework?

I have a model named Article and a few api views for it. They are divided for diffrent purposes (for example ArticleUpdateAPI class for UPDATE http method, ArticleDeleteAPI for DELETE method etc). In urls.py they are separated to diffrent endpoints (aritcle/pk/update, /article/pk/delete etc).
As I know, it's not good practice to build endpoint like this, so I want to bind them to single url and use diffrent classes for handling diffrent http methods. Is it possible and how? Examples are below
ArticleAPI.py
class ArticlePostAPI(generics.CreateAPIView):
serializer_class = ArticleSerializer
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({
"comment": CommentSerializer.data
}, status=201)
class ArticleRetrieveAPI(generics.RetrieveAPIView):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
permission_classes = [
permissions.AllowAny
]
class ArticleListAPI(generics.ListAPIView):
serializer_class = ArticleSerializer
queryset = Article.objects.order_by('number', 'headline')
permission_classes = [
permissions.AllowAny
]
class ArticleUpdateAPI(generics.UpdateAPIView):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
permission_classes = [
permissions.IsAuthenticated
]
lookup_field = 'pk'
def update(self, request, *args, **kwargs):
instance = self.get_object()
if request.user != instance.author:
return Response({
"errors": "Logged in user and author must be same"
}, status=403)
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
urls.py
urlpatterns = [
...
# Article API
path('article/post/', ArticlePostAPI.as_view(), name='article_creation'),
path('article/<int:pk>/', ArticleRetrieveAPI.as_view(), name='article_retrieve'),
path('article/', ArticleListAPI.as_view(), name='article_list'),
path('article/<int:pk>/update/', ArticleUpdateAPI.as_view(), name='article_update'),
]
Use ModelViewSet for simplifying it.
Using this you can perform all http method actions with a single url and single view.
from rest_framework import viewsets
class ArticleView(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
permission_classes = [permissions.AllowAny]
urls.py
router = DefaultRouter()
router.register(r'articles', views.ArticleView, basename='articles')
here
GET - /articles/ - gives list of all articles
GET - /articles/id/ - gives one object
POST - /articles/ - creates an article
PUT - /articles/id/ - updates an article
PATCH - /articles/id/ - partially updates an article
DELETE - /articles/id/ - deletes an article

DRF update model with many-to-many field

I have two models with many-to-many relationship and I also have nested routers between them.
When I'm trying to create a tag at the endpoint api/page/4/tags/, tag is created in the database for tags, but nothing happend in my table for many to many relationship. How can I fix it?
I want to update my M2M everytime a new tag is created. Thank you
models.py
class Tag(models.Model):
title = models.CharField(max_length=30, unique=True)
class Page(models.Model):
...
tags = models.ManyToManyField(Tag, related_name='pages')
My serializers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class PageSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True)
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Page
fields = ['id', 'title', 'uuid', 'description', 'owner', 'is_private', 'tags']
views.py
class PageViewSet(viewsets.ModelViewSet):
queryset = Page.objects.all()
serializer_class = PageSerializer
def perform_update(self, serializer):
serializer.save(owner=self.request.user)
class TagViewSet(viewsets.ModelViewSet):
serializer_class = TagSerializer
queryset = Tag.objects.all()
class NestedTagViewSet(CreateModelMixin, ListModelMixin, GenericViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = (IsAuthenticatedOrReadOnly,)
def get_page(self, request, page_pk=None):
page = get_object_or_404(Page.objects.all(), pk=page_pk)
self.check_object_permissions(self.request, page)
return page
def create(self, request, *args, **kwargs):
self.get_page(request, page_pk=kwargs['page_pk'])
return super().create(request, *args, **kwargs)
def get_queryset(self):
return Tag.objects.filter(pages=self.kwargs['page_pk'])
def list(self, request, *args, **kwargs):
self.get_page(request, page_pk=kwargs['page_pk'])
return super().list(request, *args, **kwargs)
my urls.py
from django.urls import path, include
from rest_framework_nested import routers
from .views import PageViewSet, TagViewSet, NestedTagViewSet
router = routers.SimpleRouter()
router.register(r'pages', PageViewSet)
pages_router = routers.NestedSimpleRouter(router, r'pages', lookup='page')
pages_router.register(r'tags', NestedTagViewSet, basename='page-tags')
app_name = 'page'
urlpatterns = [
path(r'', include(router.urls)),
path(r'', include(pages_router.urls)),
]
I'm trying to create new tags at the api/ ^pages/(?P<page_pk>[^/.]+)/tags/$ [name='page-tags-list'] host
I will assume after you create a tag you want to add it to the current page in the route.
With this in mind, you have a tag viewset, so you need to override the create method when you are creating a tag.
class TagViewSet(viewsets.ModelViewSet):
serializer_class = TagSerializer
queryset = Tag.objects.all()
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
new_tag = Tag.objects.get(id=response["id"])
page = Page.objects.get(id=kwards["page_pk"])
page.tags.add(new_tag)
return response
The idea is to create a tag and after you create it, append it to the page M2M relationship. Hope this helps.

How to automatically generate django rest framework documentation with parameters?

I use redoc + drf_yasg to generate the documentation of my api made with django-rest-framwork.
It works well as soon as I add an entry point it appears well in the documentation.
However, I don't understand how to add in the documentation the search parameters that can be given during the query.
For example, in the list function, I can pass 2 optional parameters, an id and name:
class TechnicalDataViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving machine.
"""
permission_classes = [permissions.IsAuthenticated]
def list(self, request):
id_machine = self.request.query_params.get('id_machine')
name = self.request.query_params.get('name')
queryset = TechnicalData.objects.all()
if id_machine:
queryset = queryset.filter(machine__id=id_machine)
if name:
queryset = queryset.filter(name=name)
serializer = TechnicalDataSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = TechnicalData.objects.all()
technical_data = get_object_or_404(queryset, pk=pk)
serializer = TechnicalDataSerializer(technical_data)
return Response(serializer.data)
I don't see any mention of the parameters in the documentation. How can I add them?
I don't know if this helps but here is how I implemented redoc:
schema_view = get_schema_view(
openapi.Info(
title="test API",
default_version='v1',
description=f"""
# Public API
**Reserved to authenticated users**
Token Lifetime :
* ACCESS_TOKEN_LIFETIME : {duration(settings.SIMPLE_JWT.get('ACCESS_TOKEN_LIFETIME'))}
* REFRESH_TOKEN_LIFETIME : {duration(settings.SIMPLE_JWT.get('REFRESH_TOKEN_LIFETIME'))}
### Links
[Get access token](************)
### Format
Use `?format=` option in url:
* `?format=api` (default)
* `?format=json`
""",
terms_of_service="",
contact=openapi.Contact(email="***********"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('docs/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
You have to add your parameters to your view if you want to give info about them. I use drf_yasg with swagger but maybe this might help.
Here's an example.
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
class TechnicalDataViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving machine.
"""
permission_classes = [permissions.IsAuthenticated]
id_machine = openapi.Parameter('id_machine', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False)
name = openapi.Parameter('account', openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False)
#swagger_auto_schema(manual_parameters=[id_machine, name])
def list(self, request):
id_machine = self.request.query_params.get('id_machine')
name = self.request.query_params.get('name')
queryset = TechnicalData.objects.all()
if id_machine:
queryset = queryset.filter(machine__id=id_machine)
if name:
queryset = queryset.filter(name=name)
serializer = TechnicalDataSerializer(queryset, many=True)
return Response(serializer.data)
#swagger_auto_schema(manual_parameters=[id_machine, name])
def retrieve(self, request, pk=None):
queryset = TechnicalData.objects.all()
technical_data = get_object_or_404(queryset, pk=pk)
serializer = TechnicalDataSerializer(technical_data)
return Response(serializer.data)

Inconsistent file paths in django rest framework

I have the following view:
class UserProfileView(APIView):
permissions_classes = [permissions.IsAuthenticated]
def get(self, request):
user = User.objects.get(id=request.user.id)
serializer = UserPrivateSerializer(user)
return Response(serializer.data)
The following Model:
class User(AbstractUser):
pp = models.ImageField(blank=True)
and the following serializer:
class UserPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
The following urls:
urlpatterns = [
path('profile/', UserProfileView.as_view())
]
I response I get is:
{
"pp": "/media/WIN_20190423_18_50_32_Pro.jpg"
}
when I expect:
{
"pp": "localhost:8000/media/WIN_20190423_18_50_32_Pro.jpg"
}
I know it's not a model or serializer issue because I have other views that use the same model and serializer where it returns the full path.
try this:
class UserProfileView(APIView):
permissions_classes = [permissions.IsAuthenticated]
def get(self, request):
user = User.objects.get(id=request.user.id)
serializer = UserPrivateSerializer(user, context=self.get_serializer_context())
return Response(serializer.data)
the key is add context=self.get_serializer_context() to you serializer.
It turns out all I do was to add context={"request": request} to the serializer.

DjangoRestFramework - How to filter ViewSet's object list based on end-users input?

This is my ViewSet:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.order_by('-createdAt')
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user, location=self.request.user.userextended.location)
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self,
'location': self.request.user.userextended.location
}
#detail_route(methods=['post'], permission_classes=[IsFromLocationOrReadOnly])
def like(self, request, pk=None):
post = self.get_object()
post.usersVoted.add(request.user)
return Response(status=status.HTTP_204_NO_CONTENT)
This is my router / urls.py:
router = routers.DefaultRouter()
router.register(r'posts', views.PostViewSet)
So when I go to /posts I get a list of all posts. What I want is to be able to allow the end-user to go to a specific URL like so: /posts/username and when he does, I want to give him all the posts of that specific username (the filtering will be simple. Something along these lines:
queryset = Post.objects.filter(username=usernameProvidedByTheURL)
How do I go about doing this? Is it possible using DRF?
In your url:
url(r'^/post/(?P<username>\w+)/?$', PostViewSet.as_view({'get': 'list'})),
Then in your PostViewSet, overwrite the get_queryset() method to filter the data by username
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.order_by('-createdAt')
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)
def get_queryset(self):
username = self.kwargs['username']
return Post.objects.filter(username=username)
UPDATE
If you want to keep /post/ endpoint to retrieve all post. Then you need to create an extra view to handle /post/username
class PostsListByUsername(generics.ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
username = self.kwargs['username']
return Post.objects.filter(username=username)
Then in your urls.py
url(r'^/post/(?P<username>\w+)/?$', PostsListByUsername.as_view()),
Note:
In your get_serializer_context method, you don't need to return request, format and view. DRF will append it for you.
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'location': self.request.user.userextended.location
}