How do I pass a parent id as an fk to child object's ModelForm using generic class-based views in Django?

China☆狼群 提交于 2019-11-28 06:07:51

问题


I am trying to use Django Generic Class-Based Views to build a CRUD interface to a two-model database. I have a working CRUD interface to the parent model, and am stuck trying to get the child Create working. For consistency with other Django examples, take the parent to be Author and the child to be Book. What is the simplest way to allow users to add Books to an Author?

In HTML terms, I think that I want to make a link on the Author detail page that includes the ID of the Author, have that ID be pre-set on the Book form, and then have the Book form processing use that ID as the PK of the Book. But I don't understand how to use Django to make this happen. I have read through https://docs.djangoproject.com/en/1.6/topics/class-based-views/generic-editing/, How do I use CreateView with a ModelForm, How do I set initial data on a Django class based generic createview with request data, and Set initial value to modelform in class based generic views, each of which seems to answer a slightly different question.

Here is the relevant code:

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>

Questions

1) How do I get the Author ID from the Book page URL to the Author form, and then processed correctly? With the sample code above, the Django debugger shows that it's present in this way:

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

but I don't understand how to grab that variable out of the ... context? ... and put it into the form.

1a) Can I make it a url parameter instead of part of the URL itself, i.e., book/create?author=1234 instead of book/create/1234/? Or even make the whole thing a POST so that it's not part of the URL? Which is the best practice, and how is it done?

2) Once the variable is in the form, how can it be present as a hidden input, so that the user doesn't have to see it?


回答1:


With the url that you defined in author_detail.html the author_id variable will be accessible in the view as 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

Then in your form you can add:

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)

Then in the template:

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

The form_valid in the view should retrieve the id, get the appropriate author and set that author as the books author. The commit=False prevents the model getting saved at first while you set the author and calling super will result in form.save(commit=True) being called.




回答2:


You could pass the author id to the form, here's some directions:

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:


I had a similar situation and, when doing the accepted answer steps I encountered 2 errors (I'm using Python 2.7):

  1. object has no attribute 'fields' which was fixed by using answer to a similar question: https://stackoverflow.com/a/8928501/2097023 from @scotchandsoda:

...self.fields should be placed before calling super(...)

def __init__(self, users_list, **kw):
    super(BaseWriteForm, self).__init__(**kw)
    self.fields['recipients'].queryset = User.objects.filter(pk__in=users_list)
  1. object has no attribute 'get' which was fixed using answer: https://stackoverflow.com/a/36951830/2097023 from @luke_dupin:

...this error can also be generated by incorrectly passing arguments in the init of a form, which is used for an admin model.

Example:

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

Notice the double passing of self? It should be:

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


来源:https://stackoverflow.com/questions/24978121/how-do-i-pass-a-parent-id-as-an-fk-to-child-objects-modelform-using-generic-cla

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!