I am using bootstrap-modal-forms to show a user a formset with some inline forms. It is possible for the user to save the form if data is only entered into the original form, but if the inline formset has data then I get the following error:
'NoneType' object has no attribute 'is_ajax'
The inline formset was working correctly before I tried to implement them in the modal form. The problem seems to arise only when the inline formset (projectimages) is saved it is a NoneType.
My views.py
class ProjectCreate(BSModalCreateView):
form_class = ProjectForm
template_name = 'project_form.html'
success_message = 'Success: %(project_name)s was created.'
def get_success_url(self):
return reverse_lazy('project-detail', kwargs={'project': self.object.slug})
def get_context_data(self, **kwargs):
data = super(ProjectCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['projectimages'] = ProjectFormSet(self.request.POST, self.request.FILES,)
else:
data['projectimages'] = ProjectFormSet()
return data
def form_valid(self, form):
form.instance.date_created = timezone.now()
context = self.get_context_data()
projectimages = context['projectimages']
with transaction.atomic():
self.object = form.save()
if projectimages.is_valid():
projectimages.instance = self.object
projectimages.save()
return super(ProjectCreate, self).form_valid(form)
My forms.py
class ProjectForm(BSModalForm):
class Meta:
model = Project
exclude = ['date_created', 'slug']
ProjectFormSet = inlineformset_factory(
Project,
ProjectImage,
can_delete=True,
form=ProjectForm,
extra=1,
)
My models.py
class Project(models.Model):
project_name = models.CharField(max_length=100)
date_created = models.DateTimeField('Created on')
slug = models.SlugField(unique=True)
def __str__(self):
return self.project_name
def save(self, *args, **kwargs):
self.slug = slugify(str(self))
super(Project, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('project-list')
class ProjectImage(models.Model):
image = models.ImageField(verbose_name='Additional Images', upload_to=project_directory_path)
project = models.ForeignKey(Project, on_delete=models.PROTECT)
annotation = models.CharField(max_length=200, blank=True)
I expect the user to be able to add as many images to the modal formset as they like.
The BSModalForm
expects you to initialise it with the request
. This happens in your BSModalCreateView
for the main form but not for your formset, because you initialise it manually.
So when initialising, just add the form_kwargs
attribute:
if self.request.POST:
data['projectimages'] = ProjectFormSet(
self.request.POST, self.request.FILES,
form_kwargs={'request': self.request})
else:
data['projectimages'] = ProjectFormSet(form_kwargs={'request': self.request})
Note that I think the form
you set in ProjectFormSet
is wrong, because it should be a form for a ProjectImage
model, not a Project
. It should actually be called ProjectImageFormSet
to better reflect what it is.
You probably want to remove form=ProjectForm
as it probably doesn't need to be a BSModalForm
(not sure about that). In that case you should not pass the request
in form_kwargs
. If not, you just need to create another ProjectImageForm
class.
Finally, you should not return super().form_valid()
because that will save the main form a second time (you already did). Do the redirect yourself.