I am using the django DeleteView class to delete different entries. The only thing that differs from call to call is the model attribute. It there anyway to have only one call for delete for different models? My suggestion is something along these lines but I don't know how to implement it. Any suggestions?
views.py:
class Delete(DeleteView):
template_name='kammem/delete.html'
success_url=reverse_lazy('forl')
model=super().get_context_data()
def get_context_data(self,**kwargs):
mod=self.kwargs['model']
if mod=='forening':
model=forening
elif mod=='person'
return model
urls.py:
path('delete/<int:pk>/<str:model>',Delete.as_view(),name='delete'),
Related
Bit of a strange one and wondering if anyone else here has come across this.
I have a standard DeleteView with the GET showing a confirmation page containing a form that posts to the delete view.
Whenever I click confirm nothing happens - the post to the view occurs and it redirects as intended, however the object is not deleted.
If I then perform the action a second time the object is deleted.
class MetricDeleteView(DeleteView):
template_name = "dashboard/administration/metric/delete.html"
button_title = "Update metric"
form_class = MetricUpdateForm
model = dashboard_metric
#cached_property
def dashboard_score(self):
return self.get_object().score
def get_success_url(self):
return reverse_lazy("administration:dashboard:update_score", kwargs={
'dashboard': self.dashboard_score.dashboard.id,
'pk': self.dashboard_score.id
})
I can't for the life of me figure out why this is occurring across all some models on my site.
Hm, interesting. As the view is generic, have you looked at the model to check it doesn't override the delete functionality? Perhaps it doesn't delete on the first pass and sets a variable to 'deleted' instead, especially if you're working with syncing across platforms. WatermelonDB, for instance. Nathan. :D
So i'm working on an inventory app, a django-app where you can handle inventory in a company. I've been using mostly class-based so far in my views.py, and i can already create and edit all of my models using class-based views.
So when it comes to the generic.DeleteView, i have some problems.
This is my function in views.py:
class DeleteItemView(DeleteView):
model: Item
success_url: reverse_lazy('inventory_app:items')
template_name = 'inventory/detail_pages/item_detail.html'
And this is my URL to the function:
path('items/<int:pk>/delete/', views.DeleteItemView.as_view(), name='delete_item')
When i call this url with a button, this error appears:
DeleteItemView is missing a QuerySet. Define DeleteItemView.model, DeleteItemView.queryset, or override DeleteItemView.get_queryset().
So i heard online that this appears when /<int:pk>/ is missing in the url. But i have it in mine, so whats the problem here?
Thank you already
class DeleteItemView(DeleteView):
model = Item
success_url = reverse_lazy('inventory_app:items')
template_name = 'inventory/detail_pages/item_detail.html'
remove the colon (:) and change to =
I have a model named Post and have a field there called owner (foreign key to User). Of course, only owners can update or delete their own posts.
That being said, I use login_required decorator in the views to make sure the user is logged in but then, I also need to make sure the user trying to update/delete the question is the owner.
As I'm using Django: Generic Editing Views the documentation says I need to use Django: UserPassesTestMixin.
This validation will be done for the update and delete views. DRY, what is the way to go about this? should I create a class named TestUserOwnerOfPost and create a test_func() and then make the update and delete views inherit from it?
Cause that's what I have tried and didn't work, code below:
from django.views.generic.edit import UpdateView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import UserPassesTestMixin
class TestUserOwnerOfPost(UserPassesTestMixin):
def test_func(self):
return self.request.user == self.post.owner
class EditPost(UpdateView, TestUserOwnerOfPost):
model = Post
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(EditPost, self).dispatch(*args, **kwargs)
With the code above, every logged-in user in the system can edit/delete any post. What am I doing wrong? am I missing something? thanks.
The first problem is that the order of the classes you inherit is incorrect, as #rafalmp says.
However, fixing that doesn't solve the problem, because the UserPassesTest mixin performs the test before running the view. This means that it's not really suitable to check the owner of self.object, because self.object has not been set yet. Note I'm using self.object instead of self.post -- I'm don't think that the view ever sets self.post but I might be wrong about that.
One option is to call self.get_object() inside the test function. This is a bit inefficient because your view will fetch the object twice, but in practice it probably doesn't matter.
def test_func(self):
self.object = self.get_object()
return self.request.user == self.object.owner
Another approach is to override get_queryset, to restrict it to objects owned by the user. This means the user will get a 404 error if they do not own the object. This behaviour is not exactly the same as the UserPassesTestMixin, which will redirect to a login page, but it might be ok for you.
class OwnerQuerysetMixin(object):
def get_queryset(self):
queryset = super(OwnerQuerysetMixin, self).get_queryset()
# perhaps handle the case where user is not authenticated
queryset = queryset.filter(owner=self.request.user)
return queryset
The order of the classes you inherit from matters. For your access control to work, it must be enforced before UpdateView is executed:
class EditPost(TestUserOwnerOfPost, UpdateView):
I want to build a generic delete view for my application. Basically I want to have the same behaviour as the standard django admin. E.g. I want to be able to delete different objects using the same view (and template).
I was looking on django docs, and looks like that DeleteViews are coupled with the models they are supposed to delete. E.g.
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
And I want to create something more generic, e.g.
class AnyDelete(DeleteView):
model = I want to have a list of models here
success_url = reverse_lazy('some-remaining-list')
The reason CBVs were invented were to solve problems like yours. As you have already written, to create your own delete view you just need to subclass DeleteView and change two properties. I find it very easy and do it all the time (the only non-dry work that I have to do is to hook it to urls.py).
In any case, if you really want to create something more generic (e.g only one view to delete every kind of model) then you'll need to use the content types framework. As you will see in the documentation, the content types framework can be used to work with objects of arbitrary models. So, in your case you can create a simple view that will get three parameters: app_label, model and pk of model to delete. And then you can implement it like this:
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404
def generic_delete_view(request, app_label, pk):
if request.method == 'POST':
my_type = ContentType.objects.get(app_label=app_label, model=model)
get_object_or_404(my_type.model_class(), pk=pk).delete()
# here you must determine *where* to return to
# probably by adding a class method to your Models
Of course in your urls.py you have to hook this view so that it receives three parameters (and then call it like this /generic_delete/application/Model/3). Here's an example of how you could hook it in your urls.py:
urlpatterns = patterns('',
# ....
url(
r'^generic_delete/(?P<app_label>\w+)/(?P<model>\w+)/(?P<pk>\d+)$',
views.generic_delete_view,
name='generic_delete'
) ,
# ...
)
If you have a list of objects and want to get the app_label and model of each one in order to construct the generic-delete urls you can do something like this:
from django.core.urlresolvers import reverse
object = # ...
ct = ContentType.objects.get_for_model(object)
generic_delete_url = reverse('generic_delete', kwargs = {
app_label=ct.app_label,
model=ct.model,
pk=object.pk
})
# so generic_delete_url now will be something like /my_app/MyModel/42
Basically what I'm trying to achieve is a multi-model django app where different models take advantage of the same views. For example I've got the models 'Car' 'Make' 'Model' etc and I want to build a single view to perform the same task for each, such as add, delete and edit, so I don't have to create a seperate view for add car, ass make etc. I've built a ModelForm and Model object for each and would want to create a blank object when adding and a pre-populated form object when editing (through the form instance arg), with objects being determined via url parameters.
Where I'm stuck is that I'm not sure what the best way to so this is. At the moment I'm using a load of if statements to return the desired object or form based on parameters I'm giving it, which get's a bit tricky when different forms need specifying and whether they need an instance or not. Although this seems to be far from the most efficient way of achieving this.
Django seems to have functions to cover most repetitive tasks, is there some magic I'm missing here?
edit - Here's an example of what I'm doing with the arguments I'm passing into the url:
def edit_object(request, object, id):
if(object==car):
form = carForm(instance = Car.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
What about using Class Based Views? Using CBVs is the best way in Django to make reusable code. For this example maybe it can be a little longer than function based views, but when the project grows up it makes the difference. Also remember "Explicit is better than implicit".
urls.py
# Edit
url(r'^car/edit/(?P<pk>\d+)/$', EditCar.as_view(), name='edit-car'),
url(r'^make/edit/(?P<pk>\d+)/$', EditMake.as_view(), name='edit-make'),
# Delete
url(r'^car/delete/(?P<pk>\d+)/$', DeleteCar.as_view(), name='delete-car'),
url(r'^make/delete/(?P<pk>\d+)/$', DeleteMake.as_view(), name='delete-make'),
views.py
class EditSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-edit.html'
class EditCar(EditSomethingMixin, UpdateView):
model = Car
form_class = CarForm
class EditMake(EditSomethingMixin, UpdateView):
model = Make
form_class = MakeForm
class DeleteSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-delete.html'
class DeleteCar(DeleteSomethingMixin, DeleteView):
model = Car
class DeleteMake(DeleteSomethingMixin, DeleteView):
model = Make
Just pass your class and form as args to the method then call them in the code.
def edit_object(request, model_cls, model_form, id):
form = model_form(instance = model_cls.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
then just pass in the correct classes and forms in your view methods
def edit_car(request,id):
return edit_object(request, Car, CarForm, id)
each method knows what classes to pass, so you eliminate the if statements.
urls.py
url(r'^car/delete/(?<pk>\d+)/', edit, {'model': Car})
url(r'^make/delete/(?<pk>\d+)/', edit, {'model': Make})
views.py
def edit(request, id, model):
model.objects.get(id=id).delete()