问题
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):
- 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)
- 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