Reputation: 2700
tldr; I need to add the required 'author' field before the forms are validated and saved.
There are two models: Documents
and Revisions
. Each Document
can have many Revisions
and each revisions as an author (User
object). Of course I don't want the user to be able to set the author ID themselves, so I need to set it myself.
On the edit page a user can modify the document title and create a new revision. There are two forms: DocumentForm
and RevisionInlineFormset
.
I cannot seem to assign an author to RevisionInlineFormset
in the post
method before we validate and save everything.
forms.py
class DocumentForm(forms.ModelForm):
class Meta:
fields = ['title', 'path']
model = Document
RevisionInlineFormset = inlineformset_factory(
Document,
Revision,
extra=1, max_num=1,
fields=('content',),
can_delete=False)
views.py
class DocUpdate(mixins.LoginRequiredMixin, generic.edit.UpdateView):
""" Edit a document. """
form_class = DocumentForm
template_name = 'spaces/document/edit.html'
def get_object(self):
try:
return Document.objects.get_by_path(self.kwargs["path"])
except ObjectDoesNotExist:
raise Http404
def get(self, request, *args, **kwargs):
""" Handles GET requests. """
self.object = self.get_object()
# Get latest revision
rev_qs = self.object.revision_set.order_by('-created_on')
if rev_qs.count():
rev_qs = rev_qs.filter(pk=rev_qs[0].pk)
form_class = self.get_form_class()
form = self.get_form(form_class)
revision_form = RevisionInlineFormset(
instance=self.object,
queryset=rev_qs)
return self.render_to_response(
self.get_context_data(form=form,
revision_form=revision_form))
def post(self, request, *args, **kwargs):
""" Handles POST requests. """
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
revision_form = RevisionInlineFormset(
self.request.POST, instance=self.object)
if (form.is_valid() and revision_form.is_valid()):
return self.form_valid(form, revision_form)
return self.form_invalid(form, revision_form)
def form_valid(self, form, revision_form):
""" All good. Finish up and save. """
self.object = form.save()
revision_form.instance = self.object
revision_form.save()
return HttpResponseRedirect(self.get_success_url())
Upvotes: 3
Views: 2212
Reputation: 1690
I would suggest approach like this:
forms.py
from .models import Comment, Post
from django.forms.models import inlineformset_factory, ModelForm, BaseInlineFormSet
class PostCommentBaseFormset(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(PostCommentBaseFormset, self).__init__(*args, **kwargs)
if user:
for form in self.forms:
if form.instance.pk is None:
form.fields['user'].initial = user #or form.initial['user'] = user
CommentFormset = inlineformset_factory(Post, Comment, extra=3, exclude=[], formset=PostCommentBaseFormset)
views.py
from .forms import CommentFormset
def post_create(request, *args, **kwargs):
...
context['formset'] = CommentFormset(instance=post, user=request.user)
...
If you want to avoid overriding 'user' field with POST data, exclude 'user' field from formset:
CommentFormset = inlineformset_factory(Post, Comment, extra=3, exclude=['user'], formset=PostCommentBaseFormset)
and change PostCommentBaseFormset like this:
class PostCommentBaseFormset(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(PostCommentBaseFormset, self).__init__(*args, **kwargs)
for form in self.forms:
if form.instance.pk is None:
form.instance.user = user
There are other ways to achieve your goal(like iterating through each new instance in view function after formset is saved or overriding the "clean" method in formset's form and so on...) but they are more complicated in my opinion.
Upvotes: 5