【问题标题】:Validation using DeleteView before deleting instance在删除实例之前使用 DeleteView 进行验证
【发布时间】:2015-04-16 12:11:48
【问题描述】:

在删除对象之前通过一些验证处理删除对象的最佳方法是什么?例如,在我的设置中有两个模型 - Game 和 Team(它们显然是相关的)。用户应该只能删除与任何游戏无关的团队。

我创建了一个用于删除团队的表单(没有任何字段)...

class TeamDeleteForm(ModelForm):
    class Meta:
        model = Team
        fields = []

    def clean(self):
        # Check to see if this team is tied to any existing games
        if self.instance.gameteams_set.exists():
            raise ValidationError("This team is tied to 1 or more games")
        return super().clean()

但后来我意识到基于类的视图 DeleteView 没有任何形式的 form_valid() 方法。我应该扩展通用 FormView 而不是 DeleteView 还是我缺少更好的方法?

【问题讨论】:

  • 你看我下面的回答了吗?
  • @SimonCharette 我做到了,谢谢。但是,我认为我使用 FormView 有更好的解决方案。仍然不理想,在我发布我的解决方案之前,我正在等待看看是否有其他人会回答。

标签: django django-forms


【解决方案1】:

对于您的特定情况,我将简单地覆盖您视图的 queryset 属性以排除 Teams 与关联的 Games。

class TeamDeleteView(DeleteView):
    queryset = Team.objects.distinct().exclude(games__isnull=False)

有一个Django ticket opened to make the DeleteView behave like other form views,但在the proposed patch 被合并和释放之前(它不会在 1.8 中实现),您必须完全覆盖视图的 delete 方法,如下所示:

class TeamDeleteView(DeleteView):
    model = Team

    def delete(request, *args, **kwargs):
        self.object = self.get_object()
        if self.object.gameteams_set.exists():
            # Return the appropriate response
        success_url = self.get_success_url()
        self.object.delete()
        return HttpResponseRedirect(success_url)

编辑:

从您接受的解决方案看来,您正试图在模型级别阻止删除。应使用PROTECT on_delete 处理程序来执行此类强制。

from django.db import models

class Team(models.Model):
    pass

class Game(models.Model):
    team = models.ForeignKey(Team, on_delete=models.PROTECT)

你仍然需要处理你认为提出的ProtectedError:

from django.db import models
from django.http.response import HttpResponseForbidden

class TeamDeleteView(DeleteView):
    model = Team

    def delete(request, *args, **kwargs):
        try:
            return super(TeamDeleteView, self).delete(
                request, *args, **kwargs
            )
        except models.ProtectedError as e:
            # Return the appropriate response
            return HttpResponseForbidden(
                "This team is tied to 1 or more games"
            )

您甚至可以像管理员一样使用e 的protected_objects 属性来显示更有意义的错误消息。

【讨论】:

  • 如果你想用 Django 的 Admin 做这个怎么办?
  • Django 的管理员会自动处理1.10+ 中的这些错误,但如果你不能使用这个版本,你可以用类似的方式覆盖你的ModelAdmin 子类的delete_selected 方法。
【解决方案2】:

我在这个场景中同时使用了 DeleteView 和 FormView。两者各有利弊。

DeleteView 很好,因为它基于 SingleObjectMixin,您可以轻松访问要删除的对象。一种很好的方法是在 get_object 中引发异常。这样您就可以在获取和发布时引发异常。

def get_object(self, qs):
  obj = super(FooView, self).get_object(qs)
  if obj.can_delete():
    return obj
  raise PermissionDenied

FormView 很好,因为您可以利用 form_invalid 和 clean 方法,但是您仍然需要做一些工作来获取对象,设置某种形式(在 deleteview 中不需要)。

这真的是你想如何解决它的问题。其他一些问题是:您是否在 GET 上引发异常,或者您是否想显示一个漂亮的页面,让用户知道他们无法删除该对象。这可以在两种视图类型中完成。

如果您有更多要点,请更新您的问题,我会更新我的回复。

【讨论】:

    【解决方案3】:

    我认为最好的方法是覆盖模型的删除方法。例如:

    class Team(models.Model):
        ...
        def delete(self, *args, **kwargs):
            if Game.objects.filter(team__pk= self.pk).exists():
                raise Exception('This team is related to a game.')  # or you can throw your custom exception here.
            super(Team, self).delete(*args, **kwargs)
    

    【讨论】:

    • 这怎么可能是公认的解决方案?这将导致错误 500,因为您正在引发未处理的异常...
    • 同意,我认为@Esteban 给出的答案是最好的。它最直接地回答了这个问题。
    • 为了更好的用户体验,您可以返回 HttpResponseForbidden() 而不是 raise Exception()
    【解决方案4】:

    另一种方法是使用 django.db IntegrityError!

    from django.db import IntegrityError
    
    class TeamDeleteView(DeleteView):
    model = Team
    
        def delete(self, request, *args, **kwargs):
            """If DB Integrity Error, display msg and redirect to list"""
            try:
                return(super().delete(request, *args, **kwargs))
            except IntegrityError:
                messages.error(request, "This team is tied to 1 or more games")
                return render(request, template_name=self.template_name, context=self.get_context_data())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-12
      • 1970-01-01
      • 1970-01-01
      • 2016-10-18
      • 2013-01-13
      • 2016-05-26
      • 2019-05-12
      • 2017-04-14
      相关资源
      最近更新 更多