【问题标题】:How do I pass a parent id as an fk to child object's ModelForm using generic class-based views in Django?如何在 Django 中使用基于类的通用视图将父 ID 作为 fk 传递给子对象的 ModelForm?
【发布时间】:2014-09-18 15:07:05
【问题描述】:

我正在尝试使用 Django Generic Class-Based Views 来构建一个到两个模型数据库的 CRUD 接口。我有一个到父模型的工作 CRUD 接口,并且一直试图让子 Create 工作。为了与其他 Django 示例保持一致,将父级设为 Author,将子级设为 Book。允许用户将书籍添加到作者的最简单方法是什么?

在 HTML 术语中,我想我想在作者详细信息页面上创建一个包含作者 ID 的链接,将该 ID 预先设置在 Book 表单上,然后让 Book 表单处理使用该ID作为书的PK。但我不明白如何使用 Django 来实现这一点。我已经通读了https://docs.djangoproject.com/en/1.6/topics/class-based-views/generic-editing/How do I use CreateView with a ModelFormHow do I set initial data on a Django class based generic createview with request dataSet initial value to modelform in class based generic views,每一个似乎都回答了一个略有不同的问题。

以下是相关代码:

models.py

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=500)

views.py

class BookCreate(CreateView):
    form_class = BookForm

    def get_success_url(self):
        return reverse('myapp:author_read',args=(self.object.author.pk))

forms.py

class BookForm(forms.Modelform):
    class Meta:
        model = Book

urls.py

url(r'^(?P<pk>\d+)/$', AuthorRead.as_view(), name='author_read'),
url(r'^book/create/(?P<author_id>\d+)/$', BookCreate.as_view(), name='book_create'),

templates/myapp/author_detail.html

...
<p><a href="{% url 'myapp:book_create' author_id=Author.pk %}">Add a book</a></p>
...

templates/myapp/book_form.html

<form action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Done">
</form>

问题

1) 如何从图书页面 URL 中获取作者 ID 到作者表单,然后正确处理?通过上面的示例代码,Django 调试器显示它以这种方式存在:

View function            Arguments      Keyword arguments         URL name
myapp.views.BookCreate   ()             {'author_id': u'1234'}    book_create

但我不明白如何从 ... 上下文中获取该变量? ...并将其放入表单中。

1a) 我可以将其设为 url 参数而不是 URL 本身的一部分,即 book/create?author=1234 而不是 book/create/1234/?或者甚至将整个内容设为 POST 以使其不是 URL 的一部分?最佳做法是什么?如何做到的?

2)一旦变量在表单中,它怎么能作为隐藏的输入出现,让用户不用看到呢?

【问题讨论】:

    标签: python django django-class-based-views


    【解决方案1】:

    使用您在author_detail.html 中定义的url,author_id 变量将在视图中作为self.kwargs['author_id'] 访问

    # views.py
    class BookCreate(CreateView):
    ...
    def form_valid(self, form):
        book = form.save(commit=False)
        author_id = form.data['author_id']
        author = get_object_or_404(Author, id=author_id)
        book.author = author
        return super(BookCreate, self).form_valid(form)
    ...
    def get_context_data(self, **kwargs):
        context = super(BookCreate, self).get_context_data(**kwargs)
        context['a_id'] = self.kwargs['author_id']
        return context
    

    然后你可以在你的表单中添加:

    class BookForm(forms.Modelform):
        ...
        def __init__(self, *args, **kwargs):
            self.fields["author_id"] = forms.CharField(widget=forms.HiddenInput())
            super(BookForm, self).__init__(self, *args, **kwargs)
    

    然后在模板中:

      <input type=hidden name="author_id" value="{{ a_id }}">
    

    视图中的 form_valid 应该检索 id,获取适当的作者并将该作者设置为书籍作者。 commit=False 防止模型在您设置作者时首先保存,调用 super 将导致调用 form.save(commit=True)

    【讨论】:

    • 我已经实现了这一点,据我所知,它传递 author_id 直到表单在提交后由 AuthorCreate 处理,然后在将 book 对象发送到数据库之前将 author_id 应用于设置 book.author。但是,书籍表单使用作者下拉输入呈现,默认显示----,当我提交时,表单无效并重新加载,因为“此字段是必填项”。如何隐藏或跳过作者字段以便提交表单?
    • 太棒了——解决了表单问题。但是,现在它停在author_id = form.author_id 行上,错误为'BookForm' object has no attribute 'author_id'。我仍然可以在我的问题中描述的请求调试信息中看到 author_id;为什么不通过?是因为 get_initial 没有在提交时执行吗?我也不确定如何调试表单以查看模板中是否接收到 author_id。 {{ author_id }} 空空如也,{{ form.author_id }} 也是如此。
    • 我更改了答案以使其更直接
    • 表单现在加载,字段被隐藏,但同样的错误发生在同一行,author_id = form.author_id:'BookForm' object has no attribute 'author_id'。我可以在调试工具栏中的 POST 数据中看到 author_id,所以我试图弄清楚如何从 form_valid 函数中获取它。另外,我花了一点时间才弄清楚 init 函数需要在 Form 的 Meta 子类中。
    【解决方案2】:

    您可以将作者 ID 传递给表单,这里有一些说明:

    class BookForm(forms.Modelform):
        author = None
    
        class Meta:
            model = Book
    
        def __init__(self, *args, **kwargs):
            self.author = kwargs.pop('author')
            super(BookForm, self).__init__(*args, **kwargs)
    
        def save(self, commit=True):
            # your custom save (returns book)
    
    
    class BookCreate(CreateView):
        form_class = BookForm
    
        def get_form_kwargs(self):
            kwargs = super(BookCreate, self).get_form_kwargs()
            kwargs['author'] = # pass your author object
    
            return kwargs
    

    【讨论】:

      【解决方案3】:

      我遇到了类似的情况,在执行接受的答案步骤时,我遇到了 2 个错误(我使用的是 Python 2.7)

      1. object has no attribute 'fields' 已通过使用类似问题的答案修复:@scotchandsoda 的https://stackoverflow.com/a/8928501/2097023

      ...self.fields 应该放在调用 super(...)

      之前
      def __init__(self, users_list, **kw):
          super(BaseWriteForm, self).__init__(**kw)
          self.fields['recipients'].queryset = User.objects.filter(pk__in=users_list)
      
      1. 对象没有属性 'get' 已使用来自 @luke_dupin 的答案:https://stackoverflow.com/a/36951830/2097023 修复:

      ...这个错误也可能是由于在表单的 init 中错误地传递参数而产生的,该表单用于管理模型。

      例子:

      class MyForm(forms.ModelForm):
          def __init__(self, *args, **kwargs):
              super(MyForm, self).__init__(self, *args, **kwargs)
      

      注意到 self 的双重传递了吗?应该是:

      class MyForm(forms.ModelForm):
          def __init__(self, *args, **kwargs):
              super(MyForm, self).__init__(*args, **kwargs)
      

      【讨论】:

      • 欢迎提供解决方案的链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么存在,然后引用最相关的部分在目标页面不可用的情况下,您要链接到的页面。 Answers that are little more than a link may be deleted。如果链接页面发生更改,仅链接的答案可能会失效。
      猜你喜欢
      • 1970-01-01
      • 2011-08-13
      • 1970-01-01
      • 1970-01-01
      • 2013-01-09
      • 1970-01-01
      • 2020-12-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多