如何在CreateView中设置ForeignKey?

57

我有一个模型:

class Article(models.Model):
    text = models.CharField()
    author = models.ForeignKey(User)

我该如何编写基于类的视图来创建一个新的模型实例,并将author外键设置为request.user

更新:

解决方案已移至下面的单独的答案


1
绝对是最佳解决方案! - glarrain
5个回答

47

我通过重写 form_valid 方法来解决了这个问题。以下是详细的样式以澄清事情:

class CreateArticle(CreateView):
    model = Article

    def form_valid(self, form):
        article = form.save(commit=False)
        article.author = self.request.user
        #article.save()  # This is redundant, see comments.
        return super(CreateArticle, self).form_valid(form)

感谢dowjones123的建议,我们可以使它更简短,这种情况在文档中有提到:mentioned in docs

class CreateArticle(CreateView):
    model = Article

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super(CreateArticle, self).form_valid(form)

1
所有其他使用HiddenInput或设置初始值的方法都可能存在安全风险。 - Irfan
5
这不是最理想的,因为它会导致表单的 save 方法被调用两次。这是那种情况之一,你最好重新实现剩余的 super 方法,而不是委派。换句话说,将 self.object 设置为新的实例,然后重定向到 self.get_success_url() - orokusaki
@orokusaki,你说得完全正确。我查看了源代码并发现form_valid除了保存(已验证)表单实例之外什么也没有做。因此,我们可以安全地删除supersave行。后者更好,因为我们不需要考虑success_url(否则我们需要明确设置它)。修正了答案。 - Vlad T.
1
@VladT。从技术上讲,这仍然是不正确的,因为表单的保存方法仍然被调用了两次(第一次使用commit=False),而表单的保存方法可能具有您不希望运行两次的逻辑(甚至只是日志记录)。如果您添加回article.save()并将super(...)行替换为return redirect(self.get_success_url()),那将是最正确的解决方案。 - orokusaki
2
一种稍微更高效的方式(不同观点可能有所不同)是将form_valid的前两行替换为form.instance.author = self.request.user - dowjones123
显示剩余3条评论

15

我刚刚遇到了这个问题,这个线程引导了我走向正确的方向(谢谢!)。根据这个Django文档页面,我们可以完全避免调用表单的save()方法:

class CreateArticle(LoginRequiredMixin, CreateView):
    model = Article

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super(CreateArticle, self).form_valid(form)

2

你应该使用 CreateView 和一个 ModelForm 来设置那个模型。在表单定义中,你可以设置 ForeignKeyHiddenInput 形式,然后在视图中使用 get_form 方法来设置你的用户的值:

forms.py:

from django import forms

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        widgets = {"user": forms.HiddenInput()}

views.py:

from django.views.generic import *
from myapp.forms import ArticleForm
from myapp.models import Article

class NewArticleView(CreateView):
    model = Article
    form_class = ArticleForm
    def get_form(self, form_class):
        initials = {
            "user": self.request.user
        }
        form = form_class(initial=initials)
        return form

我尝试了这段代码,但它没有将用户传递到视图,并且我无法保存新文章(因为“user”字段是必需的)。相反,我在CBV中使用了form_valid方法(请参见问题中的更新)。无论如何,感谢您抽出时间来帮助! - Vlad T.
我不确定你所说的“不将用户传递到视图”是什么意思?它传递了用户的ID并将其放入隐藏字段“user”中。如果您需要在视图(即模板)中的其他位置使用用户,则已经在request变量中了。 - Berislav Lopac
我再次尝试了你的代码:我在页面源代码中找不到任何隐藏的输入(除了csrf令牌)。你为什么认为这个字段会是用户的ID?为什么不是用户名? - Vlad T.
或者这只是CBV的问题?请参见http://stackoverflow.com/questions/907858/how-to-let-djangos-generic-view-use-a-form-with-initial-values#comment716057_908038 - Vlad T.
该死,我的代码错了。现在已经修复了。请注意方法名称中的错误。 - Berislav Lopac
我确认,就像 jantoniomartin 所说的那样,这段代码将 request.user.id 通过隐藏字段传递给了模板,但我无法保存表单。 - Vlad T.

2

Berislav在views.py中的代码对我不起作用。表单按预期呈现,用户值在隐藏输入中,但表单未保存(我不知道为什么)。我尝试了稍微不同的方法,这对我有效:

views.py

from django.views.generic import *
from myapp.forms import ArticleForm
from myapp.models import Article

class NewArticleView(CreateView):
    model = Article
    form_class = ArticleForm
    def get_initial(self):
        return {
            "user": self.request.user
        }

2
应该是“get_initial”吗? - Richard Corden
12
好的,如果我修改HTML代码并在表单中注入任何用户ID,那么你的方法如何防止这种攻击? - dotz
@RichardCorden 是正确的,应该是 get_initial(没有 s)。 - TKrugg

1

有些答案主要涉及到User模型外键。然而,让我们假设一个简单的场景,其中有一个包含Article模型外键的Comment模型,并且您需要为Comment创建一个CreateView,其中每个评论都将具有Article模型的外键。在这种情况下,Article id可能会出现在URL中,例如:/article/<article-id>/comment/create/。以下是如何处理这种情况的方法。

class CommentCreateView(CreateView):
    model = Comment
    # template_name, etc 


    def dispatch(self, request, *args, **kwargs):
        self.article = get_object_or_404(Article, pk=self.kwargs['article_id'])
        return super(CommentCreateView, self).dispatch(request, *args, **kwargs)


    def form_valid(self, form):
        form.instance.article= self.article    # if the article is not a required field, otherwise you can use the commit=False way
        return super(CommentCreateView, self).form_valid(form)

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