Django基于类的DeleteView示例

61

有没有人知道或能够提供一个Django类的通用DeleteView的简单示例?我想要子类化DeleteView并确保当前登录的用户在删除对象之前拥有它。非常感谢任何帮助。提前致谢。

4个回答

65

这是一个简单的例子:

from django.views.generic import DeleteView
from django.http import Http404

class MyDeleteView(DeleteView):
    def get_object(self, queryset=None):
        """ Hook to ensure object is owned by request.user. """
        obj = super(MyDeleteView, self).get_object()
        if not obj.owner == self.request.user:
            raise Http404
        return obj

注意事项:

  • DeleteView不会在GET请求时执行删除操作;这是您提供确认模板的机会(您可以在template_name类属性中指定名称),其中包含"Yes I'm sure"按钮,用于向该视图POST
  • 您可能更喜欢错误消息而不是404?在这种情况下,请重写delete方法,在get_object调用之后检查权限并返回自定义响应。
  • 不要忘记提供与(可选自定义的)success_url类属性匹配的模板,以便用户可以确认对象已被删除。

3
啊,这太有帮助了!谢谢您花时间翻译。文档里面都有,但是盯着文档看几天真的很累人。 - Lockjaw
重写调度方法并进行用户检查不是更好吗?在get_object方法中进行检查的优点是什么? - Erik
3
@Erik @DrMeers 我会覆盖 get_queryset 方法。这样做会简单得多:return self.request.user.foo_set.all()。默认的 get_object 方法将从 queryset 进行过滤,该 queryset 仅包含由 self.request.user 拥有的项目。如果未找到,则会返回 404。 - Nick
只是出于好奇,如果用户直接发布到MyDeleteView的URL会怎么样?上面解决方案中提供的视图将不会被调用,因此任何用户都可以删除任何对象,而不考虑它的权限。 - cphyc
@cphyc 在 DeletionMixin.delete 中,get_object 方法总是在代码到达 self.object.delete() 之前被调用。直接发布到 MyDeleteView 的 URL 是删除的正常方式。 - DrMeers
@DrMeers 好的!乍一看似乎有些违反直觉! - cphyc

44

我基本上是继承了一些通用的基于类的视图来实现这个目标。主要区别在于我只过滤了查询集。我不能保证这种方法是否更好或更差,但对我来说更有意义。

可以忽略 "MessageMixin" -- 它只是为了使用Django消息框架轻松地呈现消息,并针对每个视图指定一个变量。以下是我们网站所编写的代码:

视图

from django.views.generic import CreateView, UpdateView, \
        DeleteView, ListView, DetailView

from myproject.core.views import MessageMixin

class RequestCreateView(MessageMixin, CreateView):
    """ 
    Sub-class of the CreateView to automatically pass the Request to the Form. 
    """
    success_message = "Created Successfully"

    def get_form_kwargs(self):
        """ Add the Request object to the Form's Keyword Arguments. """
        kwargs = super(RequestCreateView, self).get_form_kwargs()
        kwargs.update({'request': self.request})
        return kwargs

class RequestUpdateView(MessageMixin, UpdateView):
    """
    Sub-class the UpdateView to pass the request to the form and limit the
    queryset to the requesting user.        
    """
    success_message = "Updated Successfully"

    def get_form_kwargs(self):
        """ Add the Request object to the form's keyword arguments. """
        kwargs = super(RequestUpdateView, self).get_form_kwargs()
        kwargs.update({'request': self.request})
        return kwargs

    def get_queryset(self):
        """ Limit a User to only modifying their own data. """
        qs = super(RequestUpdateView, self).get_queryset()
        return qs.filter(owner=self.request.user)

class RequestDeleteView(MessageMixin, DeleteView):
    """
    Sub-class the DeleteView to restrict a User from deleting other 
    user's data.
    """
    success_message = "Deleted Successfully"

    def get_queryset(self):
        qs = super(RequestDeleteView, self).get_queryset()
        return qs.filter(owner=self.request.user)

使用

然后,您可以轻松地创建自己的视图来使用此类型的功能。例如,我只是在我的urls.py中创建它们:

from myproject.utils.views import RequestDeleteView

#...

url(r'^delete-photo/(?P<pk>[\w]+)/$', RequestDeleteView.as_view(
                   model=Photo,
                   success_url='/site/media/photos',
                   template_name='site/media-photos-delete.html',
                   success_message='Your Photo has been deleted successfully.'
                   ), name='fireflie-delete-photo-form'),

表单

需要注意的是:我重载了get_form_kwargs()方法,以便为我的表单提供'request'实例。如果您不想将请求对象传递给表单,只需删除这些重载方法即可。如果您想使用它们,请参照以下示例:

from django.forms import ModelForm

class RequestModelForm(ModelForm):
    """
    Sub-class the ModelForm to provide an instance of 'request'.
    It also saves the object with the appropriate user.
    """
    def __init__(self, request, *args, **kwargs):
        """ Override init to grab the request object. """
        self.request = request
        super(RequestModelForm, self).__init__(*args, **kwargs)

    def save(self, commit=True):
        m = super(RequestModelForm, self).save(commit=False)
        m.owner = self.request.user
        if commit:
            m.save()
        return m

这比你要求的内容多一点,但知道如何对 Create 和 Update 视图执行相同操作很有帮助。这种通用方法还可以应用于 ListView 和 DetailView。

MessageMixin

以防万一,如果有人想使用我使用的 MessageMixin。

class MessageMixin(object):
    """
    Make it easy to display notification messages when using Class Based Views.
    """
    def delete(self, request, *args, **kwargs):
        messages.success(self.request, self.success_message)
        return super(MessageMixin, self).delete(request, *args, **kwargs)

    def form_valid(self, form):
        messages.success(self.request, self.success_message)
        return super(MessageMixin, self).form_valid(form)

3
现在有django.contrib.messages.views.SuccessMessageMixin。https://code.djangoproject.com/ticket/16319 - pymarco
要将请求实例添加到您的上下文中,通常使用django.template.context_processors.request上下文处理器。 - Risadinha

7
最简单的方法是预先过滤查询集:
from django.views.generic import DeleteView


class PostDeleteView(DeleteView):
    model = Post
    success_url = reverse_lazy('blog:list_post')

    def get_queryset(self):
        owner = self.request.user
        return self.model.objects.filter(owner=owner)

2
我喜欢这个答案,你只查询了一次对象(本页上的某些解决方案需要双重查找),如果对象被过滤出用户范围,则会自动引发Http404错误,无需额外的用户身份验证逻辑。 - xref
这真是太聪明了;请允许我问一个问题,从用户体验设计的角度来看,这个结果是否应该显示404错误页面,还是其他什么?谢谢! - Carlo
@Carlo 这会引发一个 404 错误,这对我来说并不理想。你至少可以自定义 404 模板:https://docs.djangoproject.com/en/4.1/ref/views/#error-views - Nathaniel Hoyt
@NathanielHoyt 谢谢。我的问题更多地与显示类似于“您无权删除此内容”或类似内容而不仅仅是404页面有关。自定义方面,没错,这是绝对已知的,谢谢!我的问题更多地是从“最佳用户体验”角度考虑的。 - Carlo
1
@Carlo 我明白你的意思,也同意我不喜欢404结果。我最终采用的方法是重写get_object()方法,如果所有者没有权限,则返回None。然后我重写了post()方法来检查是否为None值对象,如果是则重定向到另一个页面,并显示有关权限的消息。这个解决方案有点笨拙,但在找到更优雅的解决方案之前它能够工作。 - Nathaniel Hoyt

1
我建议最好(也是最简单)的方法是使用UserPassesTestMixin,这将使关注点分离更加清晰。
例如:
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import DeleteView


class MyDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    def test_func(self):
        """ Only let the user access this page if they own the object being deleted"""
        return self.get_object().owner == self.request.user

2
我相信这会使对象从数据库中获取两次,第一次是在调用test_func时,第二次是在视图实际渲染时。 - xref

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接