I thought I would ask a broad question so I get a better understanding of Django and can more readily deal with any similar problems I may encounter. The specific issue which I am having is that I have written my first formset code, which renders the forms correctly but when posted no new objects are created.
I can see from the server that the form has been posted, I get no errors but no data has been added to my database (checked from django admin and manage.py shell). Perhaps it is possible to read what has been posted in a manage.py shell? I would like to be able to check whether the form posted its data correctly and it has been received by the view. Then I can see why either the data isn't posting correctly or the view isn't handling it correctly.
For the specific issue I will place my code below, in case it's a simple beginner error which one of you wizards can spot. I tried to follow and adapt this tutorial for my own purposes.
Models.py
class Chunk(models.Model):
name = models.CharField(max_length=250)
text = models.CharField(max_length=500)
images = models.FileField()
question = models.CharField(max_length=250)
expected_completion_time = models.IntegerField(default=1)
keywords = models.CharField(max_length=250, blank=True, null=True)
topic = models.CharField(max_length=250, blank=True, null=True)
course = models.CharField(max_length=250, blank=True, null=True)
is_flagged = models.BooleanField(default=False)
def get_absolute_url(self):
return reverse('detail', kwargs={'pk':self.pk})
def __str__(self):
return self.name
class Concept(Chunk):
application = models.CharField(max_length=500)
@property
def mode(self):
return "concept"
class Subconcepts(models.Model):
subconcept = models.CharField(max_length=500)
concept = models.ForeignKey(Concept, on_delete=models.CASCADE)
def __str__(self):
return self.concept.name + ' - Subconcept'
forms.py
class ConceptForm(forms.ModelForm):
# Form for creating Concept objects
class Meta:
model = Concept
fields = ['application', 'name', 'text', 'images', 'question', 'expected_completion_time', 'keywords', 'topic', 'course']
class SubconceptForm(forms.ModelForm):
# Form for creating Subconcept objects which are linked by ManyToOne fields to Concept objects
class Meta:
model = Subconcepts
fields = ['subconcept'] # Concept field excluded as will be set in view on form submission
class BaseSubconceptFormset(BaseFormSet):
def clean(self):
# Validate that all subconcepts are unique
if any(self.errors):
return
subconcepts = []
duplicates = False
for form in self.forms:
if form.cleaned_data:
subconcept = form.cleaned_data('subconcept')
if subconcept:
if subconcept in subconcepts:
duplicates = True
subconcepts.append(subconcept)
if duplicates:
raise forms.ValidationError(
'Each key feature must be unique',
code='duplicate_subconcept'
)
views.py
def testformsets(request):
# Forms for creating a concept with appropriate subconcepts
SubconceptFormset = formset_factory(SubconceptForm, formset=BaseSubconceptFormset)
if request.method == 'POST':
concept_form = ConceptForm(request.POST)
subconcept_formset = SubconceptFormset(request.POST)
if concept_form.is_valid() and subconcept_formset.is_valid():
concept = concept_form.save()
new_subconcepts = []
for subconcept_form in subconcept_formset:
subconcept = subconcept_form.cleaned_data.get('subconcept')
new_subconcepts.append(Subconcepts(subconcept=subconcept, concept=concept))
try:
with transaction.atomic():
# Add all new subconcepts at once
Subconcepts.objects.bulk_create(new_subconcepts)
# And notify our users that it worked
messages.success(request, 'You have added new material')
except IntegrityError: # If the transaction failed
messages.error(request, 'There was an error saving your concept.')
return redirect('pomodoro/index.html')
else:
concept_form = ConceptForm()
subconcept_formset = SubconceptFormset()
context = {
'concept_form': concept_form,
'subconcept_formset': subconcept_formset
}
return render(request, 'pomodoro/formset_test.html', context)
Add logging statements to your code. A good place to start is to log the outcome of every decision and the result of every important function call, like so:
def myView(request):
import logging
logging.basicConfig(filename='mylog.log', level=logging.DEBUG)
if request.method == 'POST':
logging.debug('request.method=POST')
form = MyForm(request.POST)
logging.debug('form=%s', form)
if concept_form.is_valid():
logging.debug('form is valid')
myform = form.save()
logging.debug('called form.save(), result=%s', myform)
else:
logging.debug('form is not valid')
else:
logging.debug('request.method is not POST')