Search code examples
pythondjangoformsdjango-formsformsets

Django - How can I debug code which POST's when a bug seems to be in the view?


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)

Solution

  • 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')