将模型实例保存到 ManyToMany 字段 thows AttributeError 'Post' object has no attribute 'save_m2m' but when db is checked, has saved

Saving instances of model to ManyToMany field thows AttributeError 'Post' object has no attribute 'save_m2m' but when db is checked, has saved

我的 Django 3.1.7 博客应用程序有一个带有标签字段的 Post 模型,它是 PostTag 模型的 ManyToManyField。我希望我的用户能够从一些流行的标签中 select,或者在我在 models.py 中创建的 new_tags 字段中添加他们自己的标签。 new_tags 在 Post 模型中不存在。

我的想法是抓取“新”标签的字符串,在空格处拆分它,然后检查标签是否已经存在 - 如果存在则创建它。这段代码正在运行,因为新标签正在添加到数据库中。

当我尝试将这些新标签附加到正在使用表单更新的 Post 实例时,我的问题就出现了。

我已经阅读了大量 SO 帖子,并得出了下面的视图代码。这会将新标签保存到数据库中的 Post 实例。但是,在提交表单时,它仍然会抛出一个 AttributeError: 'Post' object has no attribute 'save_m2m' 但是,如果我删除 post.save_m2m() 行,则会创建实例,但不会附加到我的 Post 实例。

views.py

class EditPostView(LoginRequiredMixin, UpdateView):
    """ Renders view to edit an existing post """
    model = Post
    template_name = 'edit_post.html'
    context_object_name = 'post'
    form_class = EditPostForm

    def form_valid(self, form):
        
        post = form.save(commit=False)

        new_tags = self.request.POST.get('new_tags')
        tags_list = new_tags.split()

        for new_tag in tags_list:
            post.tags.add(PostTag.objects.get_or_create(name=new_tag)[0])

        post.save_m2m()

        return super().form_valid(form)

    def get_success_url(self):
        
        return reverse(
            'post_detail',
            kwargs={'pk': self.object.pk, 'slug': self.object.slug})

我对代码为何有效感到困惑,但仍然会抛出错误。我能做些什么来解决它! 非常感谢任何关于我做错了什么或我可以从这里去哪里的建议!!

forms.py

class EditPostForm(forms.ModelForm):
    """
    Create form for edit post pg
    """
    ...
    new_tags = forms.CharField(
        max_length=300, required=False,
        widget=forms.TextInput(
            attrs={
                'pattern': '[a-zA-Z0-9 ]+',
            }
        ))

    class Meta:
        model = Post
        fields = [
            'title',
            ...
            'tags',
            'new_tags'
        ]
        widgets = {
            'tags': forms.CheckboxSelectMultiple
        }

models.py

我已经从 Post 模型中删除了额外的字段,以免使问题复杂化。如果需要,您可以查看完整模型here

class PostTag(models.Model):
    """
    Create tags for posts.
    """
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['name']

class Post(models.Model):
    """
    Create an instance of Post
    """
    
    title = models.CharField(
        max_length=70,
        unique=True,
        error_messages={
            'unique': "This post title already exists, please choose another."
        })
    
    tags = models.ManyToManyField(
        PostTag, related_name='post_tags', blank=True)
    
    ...

    def __str__(self):
        return f"{self.title}"

.save_m2m(…)form 的方法,而不是 Post 模型的方法,因此您应该使用:

调用它
class EditPostView(LoginRequiredMixin, UpdateView):
    # …

    def form_valid(self, form):
        post = form.save(commit=False)
        new_tags = self.request.POST.get('new_tags')
        tags_list = new_tags.split()

        for new_tag in tags_list:
            post.tags.add(PostTag.objects.get_or_create(name=new_tag)[0])
        <b>form</b>.save_m2m()
        return super().form_valid(form)

EDIT:这还不够,因为 form.save_m2m() 将在多对多关系上调用 .set(),从而删除旧的项目。我建议将其重写为:

from django.http import <b>HttpResponseRedirect</b>

class EditPostView(LoginRequiredMixin, UpdateView):
    # …

    def form_valid(self, form):
        post = <b>form.save()</b>  save to the database, including the tags
        new_tags = self.request.POST.get('new_tags')
        tags_list = new_tags.split()
        for new_tag in tags_list new_tags.split():
            post.tags.add(PostTag.objects.get_or_create(name=new_tag)[0])
        
        return <b>HttpResponseRedirect(</b>self.get_success_url()<b>)</b>

来自docs

To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass

在表单上调用 .save_m2m() 而不是 post.