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())
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With