Django DeleteView误删父级Project问题求助
问题描述
我有两个Django模型:Project和ProjectNotes。ProjectNotes是关联Project的笔记,它的project字段是关联Project的外键。
遇到的问题:删除项目笔记时,整个Project被一并删除了,但我只需要删除对应的笔记。我已经在ProjectNotes模型里正确设置了on_delete=models.CASCADE,所以问题应该出在视图代码里。
模型代码
class ProjectManager(models.Manager): def search(self, query=None): qs = self.get_queryset() if query is not None: or_lookup = ( Q(title__icontains=query) | Q(description__icontains=query) # Q(slug__icontains=query) ) qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups return qs def get_with_counted_notes_documents_todos(self): queryset = self.get_queryset().annotate( num_notes=Count('notes'), num_documents=Count('project_documents'), num_todos=Count('todo_group') ) return queryset class Project(models.Model): title = models.CharField(max_length= 200) description = tinymce_models.HTMLField() status = models.CharField(max_length=20, choices=PROJECT_CHOICES, default="active") date = models.DateTimeField(auto_now_add=True, null=True) created_by = models.ForeignKey(CustomUser, editable=False, null=True, blank=True, on_delete=models.RESTRICT) tags = tagulous.models.TagField(to=SiteWideTags, blank=True, related_name='projects_tags') objects = ProjectManager() def __str__(self): return self.title def get_absolute_url(self): return reverse('company_project:project_detail', args=[str(self.id)]) class ProjectNotesManager(models.Manager): def search(self, query=None): qs = self.get_queryset() if query is not None: or_lookup = ( Q(title__icontains=query) | Q(body__icontains=query) # Q(slug__icontains=query) ) qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups return qs class ProjectNotes(models.Model): title = models.CharField(max_length=200) body = tinymce_models.HTMLField() project = models.ForeignKey(Project, default=0, blank=True, on_delete=models.CASCADE, related_name='notes') date = models.DateTimeField(auto_now_add=True, null=True) created_by = models.ForeignKey(CustomUser, editable=False, null=True, blank=True, on_delete=models.RESTRICT) def __str__(self): return self.title def get_absolute_url(self): return reverse('company_project:project_note_detail', args=[self.project_id, str(self.id)]) objects = ProjectNotesManager()
视图代码
class ProjectNoteDeleteView(DeleteBreadcrumbMixin, DeleteView): model = ProjectNotes template_name = 'company_accounts/delete_project_note.html' @cached_property def crumbs(self): project = self.get_object() relate_notes = self.get_note() bread_crumbs = [ ("projects", reverse( "company_project:" + CompanyProjects.list_view_name, ) ), (f"{project.title}", reverse( "company_project:" + CompanyProjectsDetailView.detail_view_name, kwargs={'pk': project.id}) ), ("notes", reverse( "company_project:projectnotes_list", kwargs={'pk': project.id}) ), (f"{relate_notes.title}", reverse( "company_project:project_note_detail", kwargs={'project_pk': project.id, 'pk': self.kwargs.get('pk')}) ), (f"Delete: {relate_notes.title}", reverse( "company_project:project_note_detail", kwargs={'project_pk': project.id, 'pk': self.kwargs.get('pk')}) ) ] return bread_crumbs def get_object(self): return get_object_or_404(Project, id=self.kwargs.get('project_pk')) def get_note(self): project = self.get_object() return get_object_or_404(project.notes, id=self.kwargs.get('pk')) def related_project(self, **kwargs): project = get_object_or_404(Project, id=self.kwargs.get('project_pk')) return project def get_context_data(self, **kwargs): # Call the base implementation first to get a context context = super().get_context_data(**kwargs) context["note"] = self.get_note() context['project'] = self.get_object() return context def get_success_url(self): return reverse('company_project:project_detail', args=[(self.object.id)])
URL配置
path('project/<int:project_pk>/note/<int:pk>/delete', ProjectNoteDeleteView.as_view(), name='project_note_delete'),
模板代码
<!-- templates/company_accounts/delete_project_note.html --> {% extends 'base.html' %} {% block content %} <div class="section-container container"> <div class="general-main-column"> <div class="row"> <form method="post">{% csrf_token %} <p>Are you sure you want to permanently delete project note "{{ note }}"?</p> <input type="submit" class="btn btn-danger" value="Confirm"> </form> </div> </div> </div> {% endblock content %}
问题原因及修复方案
核心问题
你重写了DeleteView的get_object()方法,让它返回的是Project对象而不是ProjectNotes对象。DeleteView默认会删除get_object()返回的实例,所以点击删除时,实际删除的是整个项目,而不是目标笔记。
修复后的视图代码
class ProjectNoteDeleteView(DeleteBreadcrumbMixin, DeleteView): model = ProjectNotes template_name = 'company_accounts/delete_project_note.html' @cached_property def crumbs(self): note = self.get_object() project = note.project bread_crumbs = [ ("projects", reverse( "company_project:" + CompanyProjects.list_view_name, ) ), (f"{project.title}", reverse( "company_project:" + CompanyProjectsDetailView.detail_view_name, kwargs={'pk': project.id}) ), ("notes", reverse( "company_project:projectnotes_list", kwargs={'pk': project.id}) ), (f"{note.title}", reverse( "company_project:project_note_detail", kwargs={'project_pk': project.id, 'pk': note.id}) ), (f"Delete: {note.title}", reverse( "company_project:project_note_detail", kwargs={'project_pk': project.id, 'pk': note.id}) ) ] return bread_crumbs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["note"] = self.object # self.object就是当前要删除的笔记实例 context['project'] = self.object.project return context def get_success_url(self): return reverse('company_project:project_detail', args=[self.object.project.id])
关键修改点
- 移除自定义的
get_object()方法:让DeleteView默认根据model = ProjectNotes和URL中的pk参数,自动获取要删除的ProjectNotes实例,这个实例会被存到self.object中。 - 简化依赖逻辑:所有需要项目对象的地方,直接通过
self.object.project获取,无需额外查询。 - 修正成功跳转地址:
get_success_url中改为跳转到笔记所属项目的详情页,而不是原来错误的self.object.id(原来的self.object是Project,现在是ProjectNotes)。 - 删除冗余方法:去掉
get_note()和related_project(),因为现在可以直接通过self.object获取笔记,关联项目也能通过笔记直接拿到。
内容的提问来源于stack exchange,提问作者Finn
相关产品推荐
相关产品推荐

