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)
Related
I have a mixin which beside other things simplifies call of request.user object.
class MyMixin(LoginRequiredMixin, View):
...
leader = False
employee = False
def dispatch(self, request, *args, **kwargs):
self.leader = request.user.is_leader()
self.employee = request.user.is_employee()
return super().dispatch(request, *args, **kwargs)
...
And I have a heir of DetailView which has it's own dispatch method.
class MyDetailView(MyMixin, DetailView):
def dispatch(self, request, *args, **kwargs):
if not self.leader:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
But as you could've told it ain't working. Is there a way to elegantly call parent dispatch method from it's heir?
You could make your attributes into properties so that the order of execution does not matter. Using cached_property will mean that the property is only evaluated once for each request
from django.utils.functional import cached_property
class MyMixin(LoginRequiredMixin, View):
#cached_property
def leader(self):
return self.request.user.is_leader()
#cached_property
def employee(self):
return self.request.user.is_employee()
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
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 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
What it says on the tin. Is there a way to make a Django model read-only?
By this I mean a Django model in which once records have been created, they can't be edited.
This would be useful for a model that records transaction history.
You can override the model's save method and check whether it's an existing entity, in which case you won't save any changes:
def save(self, *args, **kwargs):
if self.id is None:
super(ModelName, self).save(*args, **kwargs)
So in this example you only save the changes when the entity has not got an id yet, which is only the case when it's a new entity that hasn't been inserted yet.
You can override the save method and not call super if you wanted to. That'd be a fairly easy way of accomplishing this.
# blatantly ripped the save from another answer, since I forgot to save original model
def save(self, *args, **kwargs):
if self.id is None:
super(ModelName, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
return
You should probably also raise an exception if a delete or update is attempting to occur instead of simply returning. You want to signal the user what is happening - that the behaviour isn't valid.
In addition to other solutions: If your main goal is to avoid write access from the admin, you can modify the used admin class so that nobody has an add/change permission:
class HistoryAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
If you don't want an attempt to modify a record to fail silently:
def save(self, *args, **kwargs):
if self.pk:
(raise an exception)
super(YourModel, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
(raise an exception)