I have the following view:
class ReadClass(generics.RetrieveUpdateDestroyAPIView):
queryset = MyCModel.objects.all()
serializer_class = MySerializer
def post(self, request, *args, **kwargs):
''' defined my post here'''
I know retrieveupdatedestroyapiview doesn't have post in it. And I have created my own post in the view here and on the front end, I see both post and put! Is there any way to remove the put.
Or is there any other way to do it better, I tried using ListCreateApi view. The problem with that is while it gives me the post functionality, it lists all the values, while I am looking for a specific pk. I cannot see any other generic view that gives me get and post functionality.
EDIT
I have added the edit as requested, try and except might seem unnecessary here at the moment, but I will add more functionality later on.
class ReadClass(generics.GenericAPIView, mixins.CreateModelMixin, mixins.RetrieveModelMixin):
queryset = MyCModel.objects.all()
serializer_class = MySerializer
def post(self, request, *args, **kwargs):
try:
s1 = MySerializer.objects.get(mRID=kwargs["pk"])
serializer = MySerializer(s1, data=request.data)
except MySerializer.DoesNotExist:
pass
if serializer.is_valid():
if flag == 0:
pass
else:
serializer.update(s1,validated_data=request.data)
else:
return Response(serializer.errors)
urlpatterns = [path('temp/<int:pk>', ReadClass.as_view(), name = " reading"),]
DRF has mixins for List, Create, Retrieve, Update and Delete functionality. Generic views just combine these mixins. You can choose any subset of these mixins for your specific needs. In your case, you can write your view like this, if you only want Create and Retrieve functionalty:
class ReadClass(GenericAPIView, CreateModelMixin, RetrieveModelMixin):
queryset = MyCModel.objects.all()
serializer_class = MySerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
This would provide default functionality for post and get requests. If you prefer, you can override post method like you did in your example to customize post requset behavior.
You can read more about mixins and generic views here
Related
I have many endpoints which use ModelViewSet to manage CRUD operations for my models.
What I am trying to do, is to add bulk create, update, and delete at these same endpoints. In other words, I want to add POST, PUT, PATCH and DELETE to the collection endpoint (e.g.: /api/v1/my-model). There is a django-rest-framework-bulk package available, but it seems to be abandoned (hasn't been updated in 4 years) and I'm not comfortable using a package in production that is no longer active.
Additionally, There are several similar questions here that have solutions, as well as blog posts I've found. However, they all seem to use the base ViewSet, or APIView, which would require re-writing all of my existing ModelViewSet code.
Finally, there is the option of using the #action decorator, however this would require me to have a separate list endpoint (e.g.- /api/v1/my-model/bulk) which I'd like to avoid.
Are there any other ways to accomplish this while keeping my existing ModelViewSet views? I've been looking at GenericViewSet and mixins, and am wondering if creating my own mixin might be the way to go. However, looking at the mixin code, it doesn't appear that you can specify an HTTP Request method to be attached to a given mixin.
Finally, I have tried creating a separate ViewSet that accepts PUT and adding it to my URLs, but this doesn't work (I get a 405 Method not allowed when I try to PUT to /api/v1/my-model). The code I tried looks like this:
# views.py
class MyModelViewSet(viewsets.ModelViewSet):
serializer_class = MyModelSerializer
permission_classes = (IsAuthenticated,)
queryset = MyModel.objects.all()
paginator = None
class ListMyModelView(viewsets.ViewSet):
permission_classes = (IsAuthenticated,)
def put(self, request):
# Code for updating list of models will go here.
return Response({'test': 'list put!'})
# urls.py
router = DefaultRouter(trailing_slash=False)
router.register(r'my-model', MyModelViewSet)
router.register(r'my-model', ListMyModelView, base_name='list-my-model')
urlpatterns = [
path('api/v1/', include(router.urls)),
# more paths for auth, admin, etc..
]
Thoughts?
I know you said you wanted to avoid adding an extra action but in my opinion it's the simplest way to update your existing views for bulk create/update/delete.
You can create a mixin that you add to your views that will handle everything, you'd just be changing one line in your existing views and serializers.
Assuming your ListSerializer look similar to the DRF documentation the mixins would be as follows.
core/serializers.py
class BulkUpdateSerializerMixin:
"""
Mixin to be used with BulkUpdateListSerializer & BulkUpdateRouteMixin
that adds the ID back to the internal value from the raw input data so
that it's included in the validated data.
"""
def passes_test(self):
# Must be an update method for the ID to be added to validated data
test = self.context['request'].method in ('PUT', 'PATCH')
test &= self.context.get('bulk_update', False)
return test
def to_internal_value(self, data):
ret = super().to_internal_value(data)
if self.passes_test():
ret['id'] = self.fields['id'].get_value(data)
return ret
core/views.py
class BulkUpdateRouteMixin:
"""
Mixin that adds a `bulk_update` API route to a view set. To be used
with BulkUpdateSerializerMixin & BulkUpdateListSerializer.
"""
def get_object(self):
# Override to return None if the lookup_url_kwargs is not present.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
if lookup_url_kwarg in self.kwargs:
return super().get_object()
return
def get_serializer(self, *args, **kwargs):
# Initialize serializer with `many=True` if the data passed
# to the serializer is a list.
if self.request.method in ('PUT', 'PATCH'):
data = kwargs.get('data', None)
kwargs['many'] = isinstance(data, list)
return super().get_serializer(*args, **kwargs)
def get_serializer_context(self):
# Add `bulk_update` flag to the serializer context so that
# the id field can be added back to the validated data through
# `to_internal_value()`
context = super().get_serializer_context()
if self.action == 'bulk_update':
context['bulk_update'] = True
return context
#action(detail=False, methods=['put'], url_name='bulk_update')
def bulk_update(self, request, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(
queryset,
data=request.data,
many=True,
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data, status=status.HTTP_200_OK)
Then you would just inherit from the mixins
class MyModelSerializer(BulkUpdateSerializerMixin
serializers.ModelSerializer):
class Meta:
model = MyModel
list_serializer_class = BulkUpdateListSerializer
class MyModelViewSet(BulkUpdateRouteMixin,
viewsets.ModelViewSet):
...
And your PUT request would just have to point to '/api/v1/my-model/bulk_update'
Updated mixins that don't require extra viewset action:
For bulk operations submit a POST request to the list view with data as a list.
class BulkUpdateSerializerMixin:
def passes_test(self):
test = self.context['request'].method in ('POST',)
test &= self.context.get('bulk', False)
return test
def to_internal_value(self, data):
ret = super().to_internal_value(data)
if self.passes_test():
ret['id'] = self.fields['id'].get_value(data)
return ret
In get_serializer() there's a check to ensure that only POST requests can be accepted for bulk operations. If it's a POST and the request data is a list then add a flag so the ID field can be added back to the validated data and your ListSerializer can handle the bulk operations.
class BulkUpdateViewSetMixin:
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
if self.request.method in ('POST',):
data = kwargs.get('data', None)
is_bulk = isinstance(data, list)
kwargs['many'] = is_bulk
kwargs['context']['bulk'] = is_bulk
return serializer_class(*args, **kwargs)
def create(self, request, *args, **kwargs):
if isinstance(request.data, list):
return self.bulk_update(request)
return super().create(request, *args, **kwargs)
def bulk_update(self, request):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(
queryset,
data=request.data,
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data, status=status.HTTP_200_OK)
I've tested that this works but I have no idea how it will affect API schema documentation.
So I have this:
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [TokenHasReadWriteScope]
queryset = User.objects.all()
serializer_class = UserSerializer
entity_name = 'user'
perm_type = {
'POST': 'create',
'GET': 'read',
'PATCH': 'update',
'DELETE': 'delete'
}
def check_permissions(self, request):
user = request.user
has_permissions = user.has_entity_permissions(
name=self.entity_name,
perm_type=self.perm_type[request.method]
)
if not has_permissions:
raise PermissionDenied
def create(self, request, *args, **kwargs):
self.check_permissions(request)
return super().create(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
self.check_permissions(request)
return super().list(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
self.check_permissions(request)
return super().update(request, *args, **kwargs)
I have a custom security server, the purpose is to centralize all the apps of the company, so when we have a new employee, we can give him access to the differents apps with different permissions in every entity and their properties from a single app instead of creating the user and give him permissions in every app.
So basically in the "check_permission" function I check for this, depending in the request method (perm_type associated a request method with a permission (CRUD))
The question:
there is a way to catch the request before enters into list, retrive, create, update or delete (Middlewears dont work because i need to know the entity type or endpoint, thats why I set the entity_name variable, but if you have a better idea is welcome)
Why overriding your action method when you already have check_permissions implemented by APIView class? (which ModelViewSet inherits from)
Simply add your peace of code by overriding it.
I am using http://www.django-rest-framework.org/
I have the scenario where I want to pass two or more variables based on that I need to fetch data from database. In the following code only pk is there which I want to replace with two other fields in database.
Also please suggest how can I write my urlconfig the same.
Views.py
class ExampleViewSet(viewsets.ReadOnlyModelViewSet):
model = myTable
def list(self, request):
queryset = myTable.objects.all()
serializer = mySerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = myTable.objects.all()
s = get_object_or_404(queryset, pk=pk)
serializer = mySerializer(s)
return Response(serializer.data)
Serializer.py
class Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = myTable
fields = ('attr1', 'attr2', 'attr3')
Here is how you would do it with the recent Django REST Framework.
Assuming your variables are in the resource URLs like so:
GET /parent/:id/child/
GET /parent/:id/child/:id/
Then:
urls.py:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'parent/(?P<parent_id>.+)/child', views.ExampleViewSet)
urlpatterns = router.urls
views.py:
class ExampleViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = Serializer
def get_queryset(self):
parent = self.kwargs['parent']
return myTable.objects.filter(parent=parent)
Where the 'parent' in the queryset part is your parent object. You may need to adjust it a little, of course, but the idea is encapsulated in the kwargs.
This solution will also save you a little code and you can make it into a full blown ModelViewSet just by subclassing it.
Hope that helps.
More here: DRF Filtering against the URL.
Here is an example of how you might implement what you want:
class ExampleViewSet(viewsets.ReadOnlyModelViewSet):
# This code saves you from repeating yourself
queryset = myTable.objects.all()
serializer_class = mySerializer
def list(self, request, *args, **kwargs):
# Get your variables from request
var1 = request.QUERY_DICT.get('var1_name', None) # for GET requests
var2 = request.DATA.get('var2_name', None) # for POST requests
if var1 is not None:
# Get your data according to the variable var1
data = self.get_queryset().filter(var1)
serialized_data = self.get_serializer(data, many=True)
return Response(serialized_data.data)
if var2 is not None:
# Do as you need for var2
return Response(...)
# Default behaviour : call parent
return super(ExampleViewSet, self).list(request, *args, **kwargs)
def retrieve(self, request, *args, **kwargs):
# Same for retrieve
# 1. get your variable xyz from the request
# 2. Get your object based on your variable's value
s = myTable.objects.get(varX=xyz)
# 3. Serialize it and send it as a response
serialized_data = self.get_serializer(s)
return Response(serialized_data.data)
# 4. Don't forget to treat the case when your variable is None (call parent method)
As for the urlconf, it depends on how you want to send your variables (get, post or through the url).
Hope this helps.
urls.py
url(
regex=r'^teach/(?P<pk>\d+?)/(?P<pk1>\d+?)/$',
view=teach_update.as_view(),
name='teach'
)
Templates
<td><a href="/teach/{{tid}}/{{i.id}}"><button type="button" class="btn
btn-warning">Update</button></a></td>
Views.py
class teach_update(view):
def get(self,request,**kwargs):
dist=self.kwargs['pk']
ddd=self.kwargs['pk1']
I'm trying to provide some additional context into the get() method in my FormView. I need get() because I need to run some logic first, check for a potential redirect. I also need access to the request object (because I need to check session data). Can't figure out how to do it. Simplified code below..
Attempt 1:
class LoginView(FormView):
template_name = 'members/login.html'
form_class = LoginForm
def get(self, request):
# check if to redirect
if self.request.session.get('user'):
return redirect('/dashboard/')
# render page with extra context
else:
context = super(LoginView, self).get(request)
context['message'] = self.request.session['message']
return context
No errors, but context does not come through in the template.
Attempt 2:
class LoginView(FormView):
template_name = 'members/login.html'
form_class = LoginForm
def get_context_data(self, request, **kwargs):
# check if to redirect
if self.request.session.get('user'):
return redirect('/dashboard/')
# render page with extra context
else:
context = super(LoginView, self).get_context_data(**kwargs)
context['message'] = self.request.session['message']
return context
Getting TypeError: get_context_data() takes exactly 2 arguments (1 given)
P.S. This work relates to a workaround Django's buggy messages middleware which seems to be working locally flawlessly but on live (Heroku) is not 100% reliable, renders on some pages only. Ugh, frustration setting in...
Ditch the request argument to the get_context_data method. You should also use the dispatch method to check if the user is logged in.
class LoginView(FormView):
template_name = 'members/login.html'
form_class = LoginForm
def dispatch(self, *args, **kwargs):
"""Use this to check for 'user'."""
if request.session.get('user'):
return redirect('/dashboard/')
return super(LoginView, self).dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
"""Use this to add extra context."""
context = super(LoginView, self).get_context_data(**kwargs)
context['message'] = self.request.session['message']
return context
I am fairly new to CBV and I have the following question:
I see that there is CreateView that would show you the "empty" form to create a new database entry and there is UpdateView that would show you the form for the existing entry for update.
What I need is some kind of mix of it: present the user with the form for the lastly viewes/updated entry, but if the database has no entries yet (e.g. new user), present the user with a default ("empty") form.
So, there are 2 points here:
Have a model that contains lastly viewed/updated entry per user: what should this model be?
Have a view that allows to present forms as specified above. Is there a generic or semi-generic way to do that in Django? What kind of CBV should I be using?
Thanks.
I haven't tested this so I'm not sure if this will work.
from django.views.generic.edit import ModelFormMixin, ProcessFormMixin
class MyView(ModelFormMixin, ProcessFormMixin):
def get(self, request, *args, **kwargs):
try:
self.object = MyModel.objects.latest("id")
except MyModel.DoesNotExist:
self.object = None
return super(MyView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
try:
self.object = MyModel.objects.latest("id")
except MyModel.DoesNotExist:
self.object = None
return super(MyView, self).post(request, *args, **kwargs)