I have RetrieveUpdateDestroyAPIView view like this.
class TaskRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
lookup_field = 'id'
serializer_class = TasksSerializer
def get_queryset(self):
query_set=Task.objects.get(id=self.kwargs['id'])
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
and my urls like this
path('task_detail/<int:id>', TaskRetrieveUpdateDestroyAPIView.as_view(), name="get_task"),
I am trying to PUT , PATCH , GET but getting same error
{
"detail": "Not found.",
"status_code": 404
}
The issue is in the function get_queryset, It expects a queryset but yours returns a single object, that's what the get function does as described here. So, you need to either set the queryset class field or use the get_queryset function.
You don't need to look up the task object yourself, that's what the generic view does for you. also you don't have to specify the method handlers(get, post, i.e.) yourself, they are already generated because you use RetrieveUpdateDestroyAPIView class. Also, since the lookup field defaults to the primary key(id), so, you could omit that, too
Try this code
class TaskRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
queryset = Task.objects.all()
serializer_class = TasksSerializer
and use pk instead of id
path('task_detail/<int:pk>', TaskRetrieveUpdateDestroyAPIView.as_view(), name="get_task")
or you could leave the lookup field as id and use it in the path function. It's pretty much the same thing, just saving some code
Related
I am using the django http condition to set the last_modified and e_tag headers.
Here is my set up:
def my_last_modified(request, *args, **kwargs):
return None
def get_etag_key(request, *args, **kwargs):
return None
class ProductsListAPIView(ListAPIView):
queryset = Product.objects.active() # I want to get this value in the functions
serializer_class = ProductSerializer
#condition(etag_func=get_etag_key, last_modified_func=my_last_modified)
def get(self, *args, **kwargs):
return super(ProductsListAPIView, self).get(*args, **kwargs)
What I want to do is get this variable queryset from the main class in the get_etag_key and my_last_modified functions.
Is there any way to go about this?
Hey I want to add if statement and according to it decide if to delete the object or not.
I could not find it online.
In general how can I add if statements to any CBV including Update for example..
This is my DeleteView func:
class PostDeleteView(LoginRequiredMixin, DeleteView):
model = Post
success_url = reverse_lazy('TheApp:post_list')
EDIT! THE SOLUTION THAT WORKED FOR ME:(Thanks to AKX)
def delete(self, request, *args, **kwargs):
if (Post.author == request.user.username):
return super().delete(request, *args, **kwargs)
else:
return HttpResponse('You are not the owner of this Post! You can not delete it!')
Well, as you know, CBVs' methods map to HTTP methods, so just override delete() and add your condition:
class SomeView(..., DeleteView, ...):
def delete(self, request, *args, **kwargs):
if request.GET.get('really') != 'true':
return HttpResponse('I knew you were just kidding!')
return super().delete(request, *args, **kwargs)
Which method should be overridden to add additional checks and redirect accordingly?
i.e. I've a DetailView for my product page, and if this product is not published (and brand has more products) I want to redirect to the brand page.
I added this check to get method and I'm calling get_object() manually and then doing my checks, but in the end I'm also calling the super().get() which calls get_object() as well, this makes the SQL run twice.
The solution I've found is overriding the get_object() method as following..
def get_object(self, queryset=None):
if not hasattr(self, 'object') or not self.object:
self.object = super().get_object(queryset=queryset)
return self.object
This doesn't feel right though, what is the best way to do checks without triggering get_object twice?
My code that calls get_object twice looks like this: without the hack above.
def get(self, request, *args, **kwargs):
product = self.get_object()
if not product.published:
if product.brand and #more products from brand exists#
return redirect(reverse('brand',
args=(product.brand.slug,)))
else:
return redirect(reverse('pages:home'))
return super().get(request, *args, **kwargs)
just for reference super().get looks like this, and I don't want to rewrite these lines.
https://ccbv.co.uk/projects/Django/1.10/django.views.generic.detail/DetailView/#get
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
I think this is cleaner - store the response of the super().get(...) call, which will also populate self.object, then redirect if necessary, or return the response stored from the super().get(...) call:
def get(self, request, *args, **kwargs):
super_response = super().get(request, *args, **kwargs)
if not self.object.published:
if self.object.brand and #more products from brand exists#
return redirect(reverse('brand',
args=(self.object.brand.slug,)))
else:
return redirect(reverse('pages:home'))
return super_response
Note that this does have the overhead of creating a valid response for an unpublished object. To avoid that, simply avoid the super call - yes, this means duplicating two lines of code from the superclass' method:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.object.published:
if self.object.brand and #more products from brand exists#
return redirect(reverse('brand',
args=(self.object.brand.slug,)))
else:
return redirect(reverse('pages:home'))
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
I'm sure there must be something I don't understand about type hierarchies and initialisation in python...
I wanted to log post bodies with django rest framework like suggested here on stackoverflow: by overriding initial and finalize_response.
This is how my mixin looks like:
class LoggingMixin(object):
"""
Provides full logging of requests and responses
"""
def finalize_response(self, request, response, *args, **kwargs):
# do the logging
if settings.DEBUG:
logger.debug("[{0}] {1}".format(self.__class__.__name__, response.data))
return super(LoggingMixin, self).finalize_response(request, response, *args, **kwargs)
def initial(self, request, *args, **kwargs):
# do the logging
if settings.DEBUG:
try:
data = request._data
logger.debug("[{0}] {1}".format(self.__class__.__name__, data))
except exceptions.ParseError:
data = '[Invalid data in request]'
super(LoggingMixin, self).initial(self, request, *args, **kwargs)
And my view:
class BulkScan(LoggingMixin, generics.ListCreateAPIView):
"""
Provides get (list all) and post (single) for scans.
"""
queryset = Scan.objects.all()
serializer_class = ScanSerializer
authentication_classes = (OAuth2Authentication,)
permission_classes = (IsAuthenticated,)
# insert the user on save
def pre_save(self, obj):
for scan in obj:
scan.user = self.request.user
def post(self, request, *args, **kwargs):
serializer = ScanSerializer(data=request.DATA, many=True)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Yet, a post or get request fails, complaining that the request.user property is not present. This must automagically be injected there. If I don't overwrite initial then everything is fine and the user is set when APIView.initial is called.
For now, I resorted to overriding get and post and then logging the content of the post body but I don't get why the user property is not set when I override the method.
Many thanks for any clarification on this matter.
You're calling the super implementation of initial wrong. Don't pass self:
super(LoggingMixin, self).initial(request, *args, **kwargs)
Hopefully that fixes it — there doesn't seem to be anything else wrong.
#carlton-gibson is correct, but there's one other thing. I had this same issue. Fixed it by calling the super()'s initial() before doing the logging.
def initial(self, request, *args, **kwargs):
# do the logging
result = super(LoggingMixin, self).initial(request, *args, **kwargs)
if settings.DEBUG:
try:
data = request._data
logger.debug("[{0}] {1}".format(self.__class__.__name__, data))
except exceptions.ParseError:
data = '[Invalid data in request]'
return result
I have a url mapping that looks like this:
url(r'^(?P<lang>[a-z][a-z])/$', MyTemplateView.as_view()),
There are only a few values that I accept for the lang capture group, that is: (1) ro and (2) en. If the user types http://server/app/fr/, I want to redirect it to the default http://server/app/en/.
How can I do this since MyTemplateView only has a method that is expected to return a dictionary?
def get_context_data(self, **kwargs):
return { 'foo': 'blah' }
I know this question is old, but I've just done this myself. A reason you may think you want to do it in get_context_data is due to business logic, but you should place it in dispatch.
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return redirect('home')
return super(MyTemplateView, self).dispatch(request, *args, **kwargs)
Keep your business logic in your dispatch and you should be golden.
Why only get_context_data?
Just set up your get handler to do a redirect if necessary.
def get(self, request, lang):
if lang == 'fr':
return http.HttpResponseRedirect('../en')
return super(MyTemplateView, self).get(request, lang)
A note from the future: it's now possible and probably simpler just to use RedirectView.
This worked for me using an UpdateView class in Django 3.1:
def get(self, request, *args, **kwargs):
if 1 == 1:
return HttpResponseRedirect(reverse_lazy("view_name_here"))
else:
return super().get(request, *args, **kwargs)
To determine this, I analyzed its base class (Cmd+Click in PyCharm), where I found the base method:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
You can find this and other methods in the Django source code: django/views/generic/edit.py